@go-to-k/cdkd 0.290.2 → 0.290.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 { b as displaySafe, 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 UNRENDERABLE } from "./logger-DTEUzYP_.js";
1
+ import { b as displayIdent, 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, x as displaySafe, y as UNRENDERABLE } from "./logger-th1myfgh.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-7XwrjRzX.js";
3
+ import { t as getCdkdVersion } from "./version-BUcQz4SU.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";
@@ -17,7 +17,7 @@ import { APIGatewayClient, GetRestApiCommand } from "@aws-sdk/client-api-gateway
17
17
  import { DescribeApiDestinationCommand, DescribeConnectionCommand, EventBridgeClient } from "@aws-sdk/client-eventbridge";
18
18
  import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
19
19
  import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
20
- import { CloudFrontClient, GetCloudFrontOriginAccessIdentityCommand } from "@aws-sdk/client-cloudfront";
20
+ import { CloudFrontClient, GetCloudFrontOriginAccessIdentityCommand, GetDistributionCommand } from "@aws-sdk/client-cloudfront";
21
21
  import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
22
22
  import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
23
23
  import { BedrockAgentCoreControlClient } from "@aws-sdk/client-bedrock-agentcore-control";
@@ -1148,7 +1148,15 @@ function formatDuration$1(ms) {
1148
1148
  * `Outputs.<Key>` the child does not declare rather than letting
1149
1149
  * `guardedPhysicalIdFallback` serve the synthetic
1150
1150
  * `arn:cdkd-local:...` physical id — that id starts with `arn:`, so the
1151
- * #1103 ARN-shape guard passes it.
1151
+ * #1103 ARN-shape guard passes it; and the LIVE-READ refusals of issue
1152
+ * [#3096](https://github.com/go-to-k/cdkd/issues/3096)
1153
+ * (`refuseUnservedAttribute`, raised by the `AWS::EC2::Instance` address /
1154
+ * DNS / zone arm, the `AWS::EC2::VPC` `DefaultSecurityGroup` arm and the
1155
+ * `AWS::CloudFront::Distribution` `DomainName` arm when the record omits
1156
+ * the attribute and the live read finds nothing or fails), plus the
1157
+ * read-less `AWS::RDS::DBProxy` / `DBProxyEndpoint` `VpcId` refusal beside
1158
+ * them — each replacing a physical id the attribute's value type can never
1159
+ * accept.
1152
1160
  * 2. NOT reachable from it, and thrown as this class only so that "deliberate
1153
1161
  * refusal" is a property of the THROW rather than of the one catch that
1154
1162
  * inspects it: `resolveSplit`'s two refusals of a non-string value (#1874),
@@ -1177,11 +1185,17 @@ function formatDuration$1(ms) {
1177
1185
  * subclass, never on this class.
1178
1186
  *
1179
1187
  * The class is deliberately NOT `markNonRetryable` at construction, unlike
1180
- * {@link ResourceUpdateNotSupportedError}: EXACTLY ONE of its throw sites is
1188
+ * {@link ResourceUpdateNotSupportedError}: two kinds of throw site are
1181
1189
  * genuinely time-dependent — the fabricated-account guard, where
1182
1190
  * `getAccountInfo` caches a fabricated answer for only 10s precisely so a
1183
- * later attempt can heal — so a constructor-level marker would wrongly make
1184
- * that one terminal. Every OTHER site marks at its own `throw`: each
1191
+ * later attempt can heal, and since issue #3096 the LIVE-READ refusals
1192
+ * (`refuseUnservedAttribute`), where a `pending` instance settles seconds
1193
+ * later and a failed describe can succeed on the next attempt — so a
1194
+ * constructor-level marker would wrongly DECLARE those terminal (whether
1195
+ * `withRetry` then retries one is the classifiers' call, and that helper's
1196
+ * doc comment says what they actually see). Every OTHER
1197
+ * site marks at its own `throw` (the #3096 `DBProxy` `VpcId` refusal
1198
+ * included — it has no live read, so it decides from the record alone): each
1185
1199
  * decides from inputs a retry cannot change (a persisted state record, an
1186
1200
  * attribute-name suffix, a CLI flag, an already-resolved value's type, a
1187
1201
  * template's literal `RoleArn`, a child stack's declared output names), and
@@ -12731,7 +12745,13 @@ const STATE_SOURCED_READBACK_RULES = {
12731
12745
  * `observedProperties`, where a mask is a REGRESSION rather than a refusal
12732
12746
  * (`cdkd export` blocks such a record and the rollback replay refuses the
12733
12747
  * operation). A caller declares this constant when it knows its bag becomes a
12734
- * drift baseline and nothing else.
12748
+ * drift baseline and nothing else — and since issue
12749
+ * [#2939](https://github.com/go-to-k/cdkd/issues/2939) the two `cdkd drift`
12750
+ * writers make that call per record: `--accept`'s new baseline and
12751
+ * `--revert`'s narrowed delta take THIS constant when the record carries
12752
+ * `observedProperties` (the bag lands there) and
12753
+ * {@link STATE_SOURCED_READBACK_RULES} otherwise (the bag lands in
12754
+ * `properties`), the *** rows of the write-site table above.
12735
12755
  */
12736
12756
  const STATE_SOURCED_BASELINE_RULES = {
12737
12757
  descendArrays: false,
@@ -12944,7 +12964,8 @@ function isPlainObject$2(value) {
12944
12964
  }
12945
12965
  /**
12946
12966
  * Stands in for a source part the skeleton cannot know — an `Fn::Join` element
12947
- * that is itself an intrinsic, or an `Fn::Sub` `${...}` variable.
12967
+ * that is itself an intrinsic or any other non-scalar or `null` part (see
12968
+ * {@link joinPartLiteralText}), or an `Fn::Sub` `${...}` variable.
12948
12969
  *
12949
12970
  * Built from {@link DYNAMIC_REFERENCE_INNER_CHAR} rather than `.` because a
12950
12971
  * recorded expression's INNER text never contains `}`: the resolver matches
@@ -13013,7 +13034,7 @@ const MAX_SKELETON_CANDIDATE_LENGTH = 512;
13013
13034
  * between them. Measured on a ~120-char candidate, before the collapse:
13014
13035
  * 4 adjacent wildcards 39ms, 5 ~1s, 6 20s, 8 did not finish in two minutes. Two
13015
13036
  * legal CFn shapes CDK can emit produce ADJACENT ones — an `Fn::Join` with an
13016
- * EMPTY delimiter and consecutive non-string parts, and an `Fn::Sub` with
13037
+ * EMPTY delimiter and consecutive intrinsic (or `null`) parts, and an `Fn::Sub` with
13017
13038
  * adjacent `${a}${b}` variables — and the collapse takes both to a single
13018
13039
  * wildcard. It does NOT close the class, which is why the cap exists beside it.
13019
13040
  *
@@ -13095,7 +13116,8 @@ function anchoredSkeletonPattern(segments) {
13095
13116
  }
13096
13117
  /**
13097
13118
  * A source part the skeleton cannot know — an `Fn::Join` element that is
13098
- * itself an intrinsic, or an `Fn::Sub` `${...}` variable. The segment form of
13119
+ * itself an intrinsic or any other non-scalar or `null` part (see
13120
+ * {@link joinPartLiteralText}), or an `Fn::Sub` `${...}` variable. The segment form of
13099
13121
  * {@link SKELETON_WILDCARD}: what a part IS, before {@link anchoredSkeletonPattern}
13100
13122
  * — the one speller — turns it into the wildcard.
13101
13123
  */
@@ -13112,13 +13134,51 @@ const UNKNOWN_PART = Symbol("unknown intrinsic part");
13112
13134
  */
13113
13135
  const UNKNOWN_PART_PLACEHOLDER = "\0";
13114
13136
  /**
13137
+ * The literal text an `Fn::Join` PART contributes, or `undefined` when the
13138
+ * part is not knowable from the template (issue #3055).
13139
+ *
13140
+ * The resolver joins every part as `String(await resolveValue(part))`, and
13141
+ * `resolveValue` returns a NUMBER or BOOLEAN unchanged, so such a part
13142
+ * contributes exactly `String(part)` -- `8080` joins as `8080`, `true` as
13143
+ * `true`. A string part is its raw text here as it always was (a
13144
+ * `{{resolve:` inside it is the token the readers look for, not text this
13145
+ * function resolves). Reading only strings as literal made the other two a
13146
+ * wildcard for the skeleton arm (a candidate differing at that position could
13147
+ * match) and a placeholder for the frame arm (whose rendered frame then no
13148
+ * longer equals the leaf's text around the middle, so the arm refuses) -- and,
13149
+ * through the frame arm's rewrite, for the nested-stack parameter recorder's
13150
+ * `frameSpellingOf`, which carries a framed parameter to the child only when
13151
+ * that arm wrote it. The recorder's own rendering of such a part does not
13152
+ * decide the case: a part outside the token leaves its one span and service
13153
+ * prefix the same either way.
13154
+ *
13155
+ * `null` stays unknowable although the resolver would join it as `null`, as it
13156
+ * was before this function existed: it is not a part a CloudFormation template
13157
+ * can carry meaningfully. Being a wildcard, it can still let the skeleton arm
13158
+ * match a lone candidate spelling something else at that position -- the
13159
+ * arm's existing wrong-reference residual, not narrowed here. The literal
13160
+ * reading reaches that same residual in a few more places: a source that left
13161
+ * two candidates matching as a wildcard can now leave ONE, which the arm
13162
+ * accepts even when the leaf's own reference was never recorded (a public
13163
+ * `ssm` parameter). An object or array part (an intrinsic, a list, or any
13164
+ * other object) stays {@link UNKNOWN_PART}.
13165
+ */
13166
+ function joinPartLiteralText(part) {
13167
+ if (typeof part === "string") return part;
13168
+ if (typeof part === "number" || typeof part === "boolean") return String(part);
13169
+ }
13170
+ /**
13115
13171
  * The text of an `Fn::Join` / `Fn::Sub` source in order: a literal part as its
13116
- * RAW text, an unknowable part as {@link UNKNOWN_PART}. ONE parser for the two
13172
+ * RAW text (a number or boolean `Fn::Join` part as the text the resolver joins
13173
+ * it as, {@link joinPartLiteralText}), an unknowable part as
13174
+ * {@link UNKNOWN_PART}. ONE parser for the three
13117
13175
  * readers of that shape — {@link intrinsicSkeletonPattern}, which hands every
13118
- * segment to {@link anchoredSkeletonPattern} to spell as a regex, and
13176
+ * segment to {@link anchoredSkeletonPattern} to spell as a regex,
13119
13177
  * {@link positionByIntrinsicFrame}, which needs the
13120
- * literal text VERBATIM to know where a token's frame begins and ends — so
13121
- * the two cannot disagree about what a source says (issue #2745).
13178
+ * literal text VERBATIM to know where a token's frame begins and ends, and
13179
+ * `frameSpellingOf` in {@link recordNestedStackParameterExpressions}, which
13180
+ * renders the same text to find the token a carried frame spells — so
13181
+ * they cannot disagree about what a source says (issues #2745, #3062).
13122
13182
  *
13123
13183
  * `undefined` for any source this cannot describe, per the pattern reader's
13124
13184
  * doc: a delimiter that is itself an intrinsic, a non-array `Fn::Join`, a
@@ -13136,7 +13196,7 @@ function intrinsicSkeletonSegments(source) {
13136
13196
  const segments = [];
13137
13197
  parts.forEach((part, index) => {
13138
13198
  if (index > 0) segments.push(delimiter);
13139
- segments.push(typeof part === "string" ? part : UNKNOWN_PART);
13199
+ segments.push(joinPartLiteralText(part) ?? UNKNOWN_PART);
13140
13200
  });
13141
13201
  return segments;
13142
13202
  }
@@ -13390,9 +13450,10 @@ function positionByCrossStackSource(bag, source, secrets) {
13390
13450
  *
13391
13451
  * One residual is worth naming rather than leaving to be rediscovered: a single
13392
13452
  * WRONG candidate can win only when this leaf's own expression is in NEITHER
13393
- * store — which is exactly an `ssm` reference whose `Type` came back
13394
- * unclassifiable (deliberately unpinned per #1901) and which then lost the
13395
- * value collapse. `recordedSecretExpressions` is process-wide, so the winner
13453
+ * store — an `ssm` reference whose `Type` came back unclassifiable
13454
+ * (deliberately unpinned per #1901) and which then lost the value collapse, or
13455
+ * a PUBLIC `ssm` reference (stored resolved, #1901) whose value coincides with
13456
+ * a recorded secret's plaintext. `recordedSecretExpressions` is process-wide, so the winner
13396
13457
  * could in principle come from another resource, and condition 3 cannot refuse
13397
13458
  * one the pass never recorded.
13398
13459
  *
@@ -13401,7 +13462,7 @@ function positionByCrossStackSource(bag, source, secrets) {
13401
13462
  * `resolveReplayProps` RE-RESOLVES the persisted expression against AWS and
13402
13463
  * hands the result to `provider.update`, so a rollback replays the wrong secret
13403
13464
  * — immediately, if the two references already resolve to different values. It
13404
- * is narrow (unclassifiable-`ssm` leaves only) but it is not cosmetic, and an
13465
+ * is narrow (unclassifiable or public `ssm` leaves only) but it is not cosmetic, and an
13405
13466
  * earlier draft of this paragraph called it "a spurious UPDATE rather than a
13406
13467
  * disclosure", which understated it.
13407
13468
  *
@@ -16981,6 +17042,7 @@ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
16981
17042
  * releases. Neither counter feeds a control decision; they are reporting only.
16982
17043
  */
16983
17044
  async function withRetry(operation, logicalId, opts = {}) {
17045
+ const shownId = displaySafe(logicalId, { asciiOnly: true }) || "<unrenderable>";
16984
17046
  const maxRetries = opts.maxRetries ?? 8;
16985
17047
  const initialDelayMs = opts.initialDelayMs ?? 1e3;
16986
17048
  const maxDelayMs = opts.maxDelayMs ?? 8e3;
@@ -17014,7 +17076,7 @@ async function withRetry(operation, logicalId, opts = {}) {
17014
17076
  if (propagationRetries > 0) spent.push(`${propagationRetries} IAM-propagation ${propagationRetries === 1 ? "retry" : "retries"} over ${(propagationSleptMs / 1e3).toFixed(2)}s of propagation backoff${budgetExhausted ? " (the full propagation budget)" : ""}`);
17015
17077
  if (nameCooldownRetries > 0) spent.push(`${nameCooldownRetries} name-cooldown ${nameCooldownRetries === 1 ? "retry" : "retries"} over ${(nameCooldownSleptMs / 1e3).toFixed(2)}s waiting for the name to be released${nameCooldownBudgetExhausted ? " (the full name-cooldown budget)" : ""}`);
17016
17078
  if (serverErrorRetries > 0) spent.push(`${serverErrorRetries} transient server-error ${serverErrorRetries === 1 ? "retry" : "retries"} (HTTP 5xx)`);
17017
- const summary = () => `${logicalId}: gave up after ${spent.join(" and ")} - ${message}` + formatRetryClassificationSignals(error);
17079
+ const summary = () => `${shownId}: gave up after ${spent.join(" and ")} - ${message}` + formatRetryClassificationSignals(error);
17018
17080
  try {
17019
17081
  opts.logger?.warn?.(summary());
17020
17082
  } catch {}
@@ -17023,7 +17085,7 @@ async function withRetry(operation, logicalId, opts = {}) {
17023
17085
  }
17024
17086
  const delay = propagation ? Math.min(250 * Math.pow(2, attempt), IAM_PROPAGATION_MAX_DELAY_MS) : nameCooldown ? Math.min(NAME_COOLDOWN_INITIAL_DELAY_MS * Math.pow(2, attempt), NAME_COOLDOWN_MAX_DELAY_MS) : Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
17025
17087
  const backoffThroughThisAttemptMs = propagation ? propagationSleptMs + delay : propagationSleptMs;
17026
- opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${attemptLimit}${propagation ? `, ${(backoffThroughThisAttemptMs / 1e3).toFixed(2)}s backoff through this attempt` : ""}) - ${message}`);
17088
+ opts.logger?.debug(` ⏳ Retrying ${shownId} in ${delay / 1e3}s (attempt ${attempt + 1}/${attemptLimit}${propagation ? `, ${(backoffThroughThisAttemptMs / 1e3).toFixed(2)}s backoff through this attempt` : ""}) - ${message}`);
17027
17089
  for (let waited = 0; waited < delay; waited += 1e3) {
17028
17090
  if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
17029
17091
  await sleep(Math.min(1e3, delay - waited));
@@ -20675,14 +20737,17 @@ async function assumeRoleForCrossAccountStateRead(roleArn) {
20675
20737
  * up the assumed-role credentials automatically without touching the
20676
20738
  * client construction sites.
20677
20739
  *
20678
- * **Why cdkd needs admin-equivalent on the assumed role.** Unlike `cdk
20679
- * deploy`, cdkd does NOT route through CloudFormation. There is no
20680
- * cfn-exec-role to delegate to. Every IAM / EC2 / Lambda / etc. API
20681
- * call is issued from the cdkd process directly. The role you pass to
20682
- * `--role-arn` (or set in `CDKD_ROLE_ARN`) MUST therefore have
20683
- * admin-equivalent permissions on the resources being deployed; CDK
20684
- * CLI's `cdk-hnb659fds-deploy-role-*` is NOT sufficient — that role
20685
- * only carries CFn + asset-publish permissions.
20740
+ * **What the assumed role must carry.** Unlike `cdk deploy`, cdkd does
20741
+ * NOT route through CloudFormation. There is no cfn-exec-role to
20742
+ * delegate to. Every IAM / EC2 / Lambda / etc. API call is issued from
20743
+ * the cdkd process directly. The role you pass to `--role-arn` (or set
20744
+ * in `CDKD_ROLE_ARN`) must therefore carry the actions for the resource
20745
+ * types being deployed plus cdkd's own bookkeeping set (Cloud Control,
20746
+ * `cloudformation:DescribeType` / `ListExports` / `DescribeStacks`,
20747
+ * `sts:GetCallerIdentity`, the state and asset storage) — i.e. exactly what the
20748
+ * caller's own principal would otherwise have needed, not
20749
+ * `AdministratorAccess`. CDK CLI's `cdk-hnb659fds-deploy-role-*` is NOT
20750
+ * sufficient — that role only carries CFn + asset-publish permissions.
20686
20751
  *
20687
20752
  * Default session duration is 1 hour. For longer-running deploys, the
20688
20753
  * caller should re-issue the cdkd command (the in-flight credentials
@@ -22663,6 +22728,36 @@ var WAFv2WebACLProvider = class {
22663
22728
  }
22664
22729
  };
22665
22730
 
22731
+ //#endregion
22732
+ //#region src/provisioning/ec2-instance-state.ts
22733
+ /**
22734
+ * The ONE spelling of "has this EC2 instance SETTLED?" (issue
22735
+ * [#3096](https://github.com/go-to-k/cdkd/issues/3096) review).
22736
+ *
22737
+ * Two readers must agree on it or a record and a resolution disagree about
22738
+ * the same instance: `EC2Provider`'s `describedInstanceAttributes` decides
22739
+ * whether a missing public member is RECORDED as the known `''` (settled) or
22740
+ * OMITTED (still `pending`, issue #3077), and the resolver's
22741
+ * `AWS::EC2::Instance` live arm decides, for an omitted member, whether to
22742
+ * SERVE that same `''` or REFUSE. Each carried the comparison inline; this
22743
+ * module is the shared predicate so a change to one side cannot leave the
22744
+ * other on the old rule.
22745
+ *
22746
+ * `pending` is the only state in which EC2 has not finished assigning the
22747
+ * launch-time members; every other reported state — `running`, `stopping`,
22748
+ * `stopped`, `shutting-down`, `terminated` — is a settled one whose public
22749
+ * pair, when absent, is genuinely absent (a private-subnet instance, or a
22750
+ * stopped one whose address was released). NO state at all is treated like
22751
+ * `pending`: a read-back that returned no instance, or a shape without one,
22752
+ * says nothing about having settled.
22753
+ *
22754
+ * A LEAF: no imports, so both `src/provisioning/**` and `src/deployment/**`
22755
+ * can take it without adding an edge between the two layers.
22756
+ */
22757
+ function isSettledInstanceState(stateName) {
22758
+ return stateName !== void 0 && stateName !== "pending";
22759
+ }
22760
+
22666
22761
  //#endregion
22667
22762
  //#region src/deployment/intrinsic-function-resolver.ts
22668
22763
  /**
@@ -23195,6 +23290,19 @@ function withoutProducerRegions(context) {
23195
23290
  return rest;
23196
23291
  }
23197
23292
  /**
23293
+ * Masked log twins registered this pass, per pass bag (issue #3100;
23294
+ * `rememberLogTwin` / `logTwinOfProduct`). Log-only: nothing that resolves or
23295
+ * persists a value reads it.
23296
+ *
23297
+ * MODULE scope, not instance scope: a region-pinned sibling resolver
23298
+ * (`resolverForProducerRegion`) resolves on behalf of the consumer with the
23299
+ * consumer's bag, so an instance-local registry left the sibling's twin where
23300
+ * the consumer's `Fn::Join` / `Fn::Sub` never looked. The key is still the
23301
+ * pass's own bag object, so scope does not widen: another pass holds another
23302
+ * bag and cannot reach these entries, and they die with the bag.
23303
+ */
23304
+ const LOG_TWINS_BY_PASS = /* @__PURE__ */ new WeakMap();
23305
+ /**
23198
23306
  * Does `value` carry a CloudFormation dynamic reference anywhere inside it?
23199
23307
  *
23200
23308
  * Used as the identity fast path of {@link
@@ -23313,10 +23421,37 @@ const recordedSecretExpressions = {
23313
23421
  * lookup (PrivateIp / PublicIp / PrivateDnsName / PublicDnsName /
23314
23422
  * AvailabilityZone). Keyed by `${physicalId}#${attributeName}`. The IP /
23315
23423
  * DNS attributes are not derivable from the instance id, so they are read
23316
- * back from AWS once per (instance, attribute) and memoized for the deploy
23317
- * lifetime.
23318
- */
23319
- const cachedEc2InstanceAttributes = {};
23424
+ * back from AWS once per (instance, attribute) and memoized for the PROCESS
23425
+ * lifetime — one `cdkd deploy` per CLI process, so "the deploy" in practice;
23426
+ * nothing in `src/` calls `resetAccountInfoCache`, tests do. Only a VALUE is cached — a settled instance's address, or the
23427
+ * known-empty `''` a settled instance reports for a public member it has
23428
+ * none of — never a refusal: a `pending` instance is re-described on the
23429
+ * next resolution, when it may have settled (issue #3096).
23430
+ */
23431
+ const cachedEc2InstanceAttributes = Object.create(null);
23432
+ /**
23433
+ * The two sibling caches of {@link cachedEc2InstanceAttributes} (issue
23434
+ * #3096), each holding a value the resolver read LIVE because the state
23435
+ * record omitted it — `definedAttributes` drops a member the provider could
23436
+ * not read back (issue #3077), so the `Fn::GetAtt` falls out of the flat
23437
+ * lookup and into `constructAttribute`'s per-type arm. Keyed by physical id
23438
+ * (AWS-minted, unique across accounts and regions, and the value — a default
23439
+ * security group id, a distribution hostname — never changes); like the
23440
+ * instance cache, a refusal caches nothing. Same process lifetime, cleared
23441
+ * together with it by {@link resetAccountInfoCache}. The RDS `DBProxy` / `DBProxyEndpoint`
23442
+ * `VpcId` arms have no cache because they have no live read: `AwsClients`
23443
+ * exposes no RDS client and this file imports none, so those two arms refuse
23444
+ * outright (see `refuseUnservedAttribute`'s callers).
23445
+ *
23446
+ * All three are NULL-PROTOTYPE objects (#3096 delta review, measured): the
23447
+ * key is a state-record physical id, and on a plain `{}` a record holding
23448
+ * `constructor` / `__proto__` / `hasOwnProperty` read a FUNCTION out of
23449
+ * `Object.prototype` as the cached value — served with zero AWS calls, ahead
23450
+ * of every shape guard below. `Object.create(null)` has nothing to read; the
23451
+ * `delete`-based clears in `resetAccountInfoCache` keep the prototype.
23452
+ */
23453
+ const cachedVpcDefaultSecurityGroups = Object.create(null);
23454
+ const cachedCloudFrontDomainNames = Object.create(null);
23320
23455
  /**
23321
23456
  * The region this call answers for: the caller's override, else the ambient one.
23322
23457
  *
@@ -25228,7 +25363,44 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25228
25363
  this.logger.warn(`Failed to fetch VPC Ipv6CidrBlocks for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
25229
25364
  return [];
25230
25365
  }
25231
- case "DefaultSecurityGroup": return resource.attributes?.["DefaultSecurityGroup"] || physicalId;
25366
+ case "DefaultSecurityGroup": {
25367
+ if (!/^vpc-[0-9a-f]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, DefaultSecurityGroup] for AWS::EC2::VPC: the state record's physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context)).slice(0, 64)}" is not a VPC id (vpc-<hex>), so cdkd will not use it as an EC2 filter value. Repair the record (cdkd import, or re-create the VPC) and deploy again.`));
25368
+ const cachedGroupId = cachedVpcDefaultSecurityGroups[physicalId];
25369
+ if (cachedGroupId !== void 0) return cachedGroupId;
25370
+ let groupId;
25371
+ try {
25372
+ groupId = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeSecurityGroupsCommand({ Filters: [{
25373
+ Name: "vpc-id",
25374
+ Values: [physicalId]
25375
+ }, {
25376
+ Name: "group-name",
25377
+ Values: ["default"]
25378
+ }] }))).SecurityGroups?.[0]?.GroupId;
25379
+ } catch (err) {
25380
+ this.refuseUnservedAttribute({
25381
+ logicalId,
25382
+ attributeName,
25383
+ resourceType,
25384
+ physicalId,
25385
+ context,
25386
+ observed: this.describeFailureObserved("DescribeSecurityGroups", err, context),
25387
+ remedy: "Fix the read (the vpc-id / group-name filtered DescribeSecurityGroups permission, or the region) and deploy again."
25388
+ });
25389
+ }
25390
+ if (groupId) {
25391
+ cachedVpcDefaultSecurityGroups[physicalId] = groupId;
25392
+ return groupId;
25393
+ }
25394
+ this.refuseUnservedAttribute({
25395
+ logicalId,
25396
+ attributeName,
25397
+ resourceType,
25398
+ physicalId,
25399
+ context,
25400
+ observed: "DescribeSecurityGroups found no group named \"default\" in the VPC",
25401
+ remedy: "Check the VPC in the console, or reference the attribute from a later deploy."
25402
+ });
25403
+ }
25232
25404
  default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25233
25405
  }
25234
25406
  if (resourceType === "AWS::IAM::Policy") switch (attributeName) {
@@ -25423,9 +25595,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25423
25595
  const cacheKey = `${physicalId}#${attributeName}`;
25424
25596
  const cached = cachedEc2InstanceAttributes[cacheKey];
25425
25597
  if (cached !== void 0) return cached;
25598
+ let value;
25599
+ let stateName;
25426
25600
  try {
25427
25601
  const instance = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeInstancesCommand({ InstanceIds: [physicalId] }))).Reservations?.[0]?.Instances?.[0];
25428
- let value;
25602
+ stateName = instance?.State?.Name;
25429
25603
  switch (attributeName) {
25430
25604
  case "PrivateIp":
25431
25605
  value = instance?.PrivateIpAddress;
@@ -25441,15 +25615,36 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25441
25615
  break;
25442
25616
  case "AvailabilityZone": value = instance?.Placement?.AvailabilityZone;
25443
25617
  }
25444
- if (value !== void 0 && value !== null && value !== "") {
25445
- cachedEc2InstanceAttributes[cacheKey] = value;
25446
- return value;
25447
- }
25448
- this.logger.warn(`DescribeInstances(${physicalId}) returned no ${this.maskSecretsForLog(attributeName, context)}; returning physical ID`);
25449
25618
  } catch (err) {
25450
- this.logger.warn(`DescribeInstances(${physicalId}) failed for ${this.maskSecretsForLog(attributeName, context)}: ${this.maskSecretsForLog(err instanceof Error ? err.message : String(err), context)}`);
25619
+ this.refuseUnservedAttribute({
25620
+ logicalId,
25621
+ attributeName,
25622
+ resourceType,
25623
+ physicalId,
25624
+ context,
25625
+ observed: this.describeFailureObserved("DescribeInstances", err, context),
25626
+ remedy: "Fix the read (the ec2:DescribeInstances permission, or the region) and deploy again."
25627
+ });
25451
25628
  }
25452
- return physicalId;
25629
+ if (value !== void 0 && value !== null && value !== "") {
25630
+ cachedEc2InstanceAttributes[cacheKey] = value;
25631
+ return value;
25632
+ }
25633
+ const settled = isSettledInstanceState(stateName);
25634
+ if (settled && (attributeName === "PublicIp" || attributeName === "PublicDnsName")) {
25635
+ cachedEc2InstanceAttributes[cacheKey] = "";
25636
+ return "";
25637
+ }
25638
+ const observedState = stateName === void 0 ? "no instance state" : `state ${stateName}`;
25639
+ this.refuseUnservedAttribute({
25640
+ logicalId,
25641
+ attributeName,
25642
+ resourceType,
25643
+ physicalId,
25644
+ context,
25645
+ observed: `DescribeInstances reports no ${this.maskSecretsForLog(attributeName, context)} yet (${observedState})`,
25646
+ remedy: settled ? "Check the instance in the console; a terminated or stopped instance has no such attribute to serve." : "Deploy without --no-wait so the instance is running before its attributes are read, or reference the attribute from a later deploy once it is."
25647
+ });
25453
25648
  }
25454
25649
  default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25455
25650
  }
@@ -25467,9 +25662,115 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25467
25662
  if (attributeName === "LaunchTemplateId") return physicalId;
25468
25663
  return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25469
25664
  }
25665
+ if (resourceType === "AWS::CloudFront::Distribution") switch (attributeName) {
25666
+ case "Id": return physicalId;
25667
+ case "DomainName": {
25668
+ if (!/^[A-Z0-9]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, DomainName] for AWS::CloudFront::Distribution: the state record's physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context)).slice(0, 64)}" is not a distribution id (upper-case alphanumerics), so cdkd will not look it up. Repair the record (cdkd import, or re-create the distribution) and deploy again.`));
25669
+ const cachedDomainName = cachedCloudFrontDomainNames[physicalId];
25670
+ if (cachedDomainName !== void 0) return cachedDomainName;
25671
+ let domainName;
25672
+ try {
25673
+ domainName = (await this.clientsForRegion(this.explicitRegion).cloudFront.send(new GetDistributionCommand({ Id: physicalId }))).Distribution?.DomainName;
25674
+ } catch (err) {
25675
+ this.refuseUnservedAttribute({
25676
+ logicalId,
25677
+ attributeName,
25678
+ resourceType,
25679
+ physicalId,
25680
+ context,
25681
+ observed: this.describeFailureObserved("GetDistribution", err, context),
25682
+ remedy: "Fix the read (the cloudfront:GetDistribution permission) and deploy again."
25683
+ });
25684
+ }
25685
+ if (domainName) {
25686
+ cachedCloudFrontDomainNames[physicalId] = domainName;
25687
+ return domainName;
25688
+ }
25689
+ this.refuseUnservedAttribute({
25690
+ logicalId,
25691
+ attributeName,
25692
+ resourceType,
25693
+ physicalId,
25694
+ context,
25695
+ observed: "GetDistribution reports no DomainName",
25696
+ remedy: "Check the distribution in the console, or reference the attribute from a later deploy."
25697
+ });
25698
+ }
25699
+ default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25700
+ }
25701
+ if (resourceType === "AWS::RDS::DBProxy" || resourceType === "AWS::RDS::DBProxyEndpoint") {
25702
+ if (attributeName === "VpcId") throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: the state record holds no VpcId for it (the read-back that would have recorded it reported none), and the physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" is a name, not a VPC id, so cdkd refuses to substitute it. Update the resource so its next deploy records the value, or reference the VPC directly.`));
25703
+ return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25704
+ }
25470
25705
  return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25471
25706
  }
25472
25707
  /**
25708
+ * Refuse an attribute the state record omits and a live read could not
25709
+ * serve (issue [#3096](https://github.com/go-to-k/cdkd/issues/3096)).
25710
+ *
25711
+ * The arms that call this all share one shape: the provider recorded no
25712
+ * value because AWS had not assigned one yet or the read-back failed (issue
25713
+ * #3077 made the key ABSENT rather than `''`), the resolver re-reads it
25714
+ * live, and the read finds nothing — or fails. Before #3096 every such arm
25715
+ * answered the PHYSICAL ID: an `i-...` where an IP address or a DNS name
25716
+ * belongs, a `vpc-...` in a security-group slot, a distribution id where a
25717
+ * hostname belongs, a proxy name in a `VpcId` position. Loud only where AWS
25718
+ * rejects the shape; an Output, an export or a free-text property carried it
25719
+ * silently. The physical id can never be the value for these attributes,
25720
+ * so the honest answer is a refusal naming the resource, the attribute, what
25721
+ * was observed and the remedy.
25722
+ *
25723
+ * DELIBERATELY NOT `markNonRetryable`, unlike every other refusal in this
25724
+ * file but the fabricated-account guard: the verdict is TIME-DEPENDENT. A
25725
+ * `pending` instance settles seconds later, and a failed describe can
25726
+ * succeed on the next attempt, so cdkd must not DECLARE it terminal — the
25727
+ * marker is that declaration, and `withRetry` honours it ahead of every
25728
+ * classifier. Leaving it off promises no retry: `isRetryableTransientError`
25729
+ * reads throttle names and HTTP statuses off the `cause` chain — which this
25730
+ * refusal deliberately does not carry, see below — and everything else by
25731
+ * message substring, and this message matches nothing on its own. So the
25732
+ * nested-stack replay (`NestedStackProvider.create` runs the child deploy
25733
+ * inside the parent's `withRetry`) re-runs the child only when an
25734
+ * interpolated hole happens to carry a pattern — the `logicalId` (the #1838
25735
+ * hazard: `DependencyViolation` and two other bare words), or the error
25736
+ * CLASS name `describeFailureObserved` puts in `observed`, which none of
25737
+ * these three reads raises — and then re-describes and can heal.
25738
+ * Measured, not designed: the fabricated-account guard sits in exactly the
25739
+ * same place. Threading a sanitized SDK error as `cause` so a throttled
25740
+ * describe classifies as transient was considered and left out — a
25741
+ * `CdkdError`'s `cause` is rendered at default verbosity by `formatError`
25742
+ * (`Caused by:`), so the clone would need its own fence. What the
25743
+ * refusal does at each consumer: a resource property fails the resource; a
25744
+ * stack Output is caught per-output by `DeployEngine.
25745
+ * handleOutputResolutionFailure`, skipped and recorded in `skippedOutputs`
25746
+ * (#2740), then re-resolved on the next deploy; an `Fn::Sub` re-raises it
25747
+ * (#1740); a `Conditions` entry still absorbs it to `false`
25748
+ * (`evaluateConditions`).
25749
+ *
25750
+ * `observed` and `remedy` are cdkd-authored sentences built by the caller
25751
+ * from an instance STATE name, an error CLASS name or a fixed phrase — never
25752
+ * AWS's raw message, which `describeFailureObserved` keeps at debug (the
25753
+ * per-reader rule in `.claude/rules/provider-resource-identity.md`: a
25754
+ * denied describe quotes the caller's account, role and session).
25755
+ */
25756
+ refuseUnservedAttribute(site) {
25757
+ const { logicalId, attributeName, resourceType, physicalId, context, observed, remedy } = site;
25758
+ throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: ${observed}. The physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" is not a usable ${this.maskSecretsForLog(attributeName, context)}, so cdkd refuses to substitute it. ${remedy}`);
25759
+ }
25760
+ /**
25761
+ * The `observed` clause of {@link refuseUnservedAttribute} for a live read
25762
+ * that THREW: names the read and the error's CLASS at default verbosity and
25763
+ * puts AWS's own text behind `--verbose`, since a denied describe quotes the
25764
+ * caller's account, role and session into its message.
25765
+ */
25766
+ describeFailureObserved(read, err, context) {
25767
+ const name = displaySafe(err instanceof Error && err.name ? err.name : "Error", { asciiOnly: true }).slice(0, 64).trim() || "Error";
25768
+ const status = err?.$metadata?.httpStatusCode;
25769
+ const errorClass = typeof status === "number" && Number.isInteger(status) ? `${name}, HTTP ${status}` : name;
25770
+ this.logger.debug(`${read} failed (${errorClass}): ${this.maskSecretsForLog(err instanceof Error ? err.message : String(err), context)}`);
25771
+ return `${read} failed (${errorClass}); re-run with --verbose for the AWS error text`;
25772
+ }
25773
+ /**
25473
25774
  * Shared unknown-attribute physicalId fallback (issues #1106 / #1111).
25474
25775
  *
25475
25776
  * Applied by BOTH the final unknown-type fallback of
@@ -25501,9 +25802,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25501
25802
  const expectsUrlShape = attributeName.endsWith("Url") && !/^https?:\/\//.test(physicalId);
25502
25803
  if (expectsArnShape || expectsUrlShape) {
25503
25804
  const expectedShape = expectsArnShape ? "an ARN (arn:...)" : "a URL (http(s)://...)";
25504
- throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${this.maskSecretsForLog(physicalId, context)}" is not ${expectedShape}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.maskSecretsForLog(attributeName, context)}.`));
25805
+ throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" is not ${expectedShape}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.maskSecretsForLog(attributeName, context)}.`));
25505
25806
  }
25506
- if (this.strictGetAtt) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${this.maskSecretsForLog(physicalId, context)}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.maskSecretsForLog(attributeName, context)}.`));
25807
+ if (this.strictGetAtt) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${displaySafe(this.maskThenStripThenMask(physicalId, context))}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${this.maskSecretsForLog(attributeName, context)}.`));
25507
25808
  this.physicalIdFallbackCount++;
25508
25809
  this.logger.warn(`Unknown attribute ${this.maskSecretsForLog(attributeName, context)} for resource type ${resourceType}, returning physical ID`);
25509
25810
  return physicalId;
@@ -25518,12 +25819,36 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25518
25819
  let values = rawValues;
25519
25820
  if (!Array.isArray(values)) values = await this.resolveValue(values, context);
25520
25821
  if (!Array.isArray(values)) throw new Error(`Fn::Join's second argument must be a list (an array literal or a list-returning intrinsic such as Fn::Cidr / Fn::GetAZs / Fn::Split / a Ref to a list-typed parameter — any List<...> type or CommaDelimitedList), but resolved to ${typeof values}`);
25521
- let result = (await allSettledKeepingFirstRejection(values.map(async (v) => {
25522
- const resolved = await this.resolveValue(v, context);
25523
- return String(resolved);
25524
- }), (pending) => this.warnAbandonedParts(pending))).join(delimiter);
25525
- if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result, context);
25526
- this.logger.debug(`Resolved Fn::Join: ${this.maskSecretsForLog(result, context)}`);
25822
+ const literalList = Array.isArray(rawValues);
25823
+ const parts = (await allSettledKeepingFirstRejection(values.map(async (v) => {
25824
+ if (typeof v === "string") return {
25825
+ ...v.includes("{{resolve:") ? await this.resolveDynamicReferencesWithLogTwin(v, v, context) : {
25826
+ result: v,
25827
+ twin: v
25828
+ },
25829
+ product: !literalList
25830
+ };
25831
+ const raw = await this.resolveValue(v, context);
25832
+ const resolved = String(raw);
25833
+ return {
25834
+ result: resolved,
25835
+ twin: resolved,
25836
+ product: true,
25837
+ raw: { value: raw }
25838
+ };
25839
+ }), (pending) => this.warnAbandonedParts(pending))).map((part) => {
25840
+ if (!part.product) return part;
25841
+ if (part.raw) return {
25842
+ result: part.result,
25843
+ twin: this.productLogTwin(part.raw.value, context)
25844
+ };
25845
+ return this.logTwinOfProduct(part, context);
25846
+ });
25847
+ let result = parts.map((part) => part.result).join(delimiter);
25848
+ let twin = parts.map((part) => part.twin).join(delimiter);
25849
+ if (result.includes("{{resolve:")) ({result, twin} = await this.resolveDynamicReferencesWithLogTwin(result, twin, context));
25850
+ this.rememberLogTwin(context, result, twin);
25851
+ this.logger.debug(`Resolved Fn::Join: ${this.maskSecretsForLog(this.logTwinText(result, twin, context), context)}`);
25527
25852
  return result;
25528
25853
  }
25529
25854
  /**
@@ -25666,11 +25991,19 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25666
25991
  async resolveSub(subArgs, context) {
25667
25992
  let template;
25668
25993
  const variables = Object.create(null);
25994
+ const variableTwins = Object.create(null);
25669
25995
  if (Array.isArray(subArgs)) {
25670
25996
  const [templateString, variableMap] = subArgs;
25671
25997
  template = templateString;
25672
25998
  if (typeof variableMap !== "object" || variableMap === null) throw markNonRetryable(/* @__PURE__ */ new Error(`Fn::Sub: the second element must be a variable map, got ${variableMap === null ? "null" : typeof variableMap}`));
25673
- for (const [key, val] of Object.entries(variableMap)) variables[key] = await this.resolveValue(val, context);
25999
+ for (const [key, val] of Object.entries(variableMap)) if (typeof val === "string") {
26000
+ const resolved = val.includes("{{resolve:") ? await this.resolveDynamicReferencesWithLogTwin(val, val, context) : {
26001
+ result: val,
26002
+ twin: val
26003
+ };
26004
+ variables[key] = resolved.result;
26005
+ variableTwins[key] = resolved.twin;
26006
+ } else variables[key] = await this.resolveValue(val, context);
25674
26007
  } else template = subArgs;
25675
26008
  const replacements = [];
25676
26009
  const matches = template.matchAll(/\$\{(!)?([^}]*)\}/g);
@@ -25678,31 +26011,39 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25678
26011
  const isEscaped = match[1] === "!";
25679
26012
  const varNameStr = match[2];
25680
26013
  if (isEscaped) {
26014
+ const escapedLiteral = `\${${varNameStr ?? ""}}`;
25681
26015
  replacements.push({
25682
26016
  match: match[0],
25683
- replacement: `\${${varNameStr ?? ""}}`
26017
+ replacement: escapedLiteral,
26018
+ twin: escapedLiteral
25684
26019
  });
25685
26020
  continue;
25686
26021
  }
25687
26022
  if (!varNameStr) {
25688
26023
  replacements.push({
25689
26024
  match: match[0],
25690
- replacement: match[0]
26025
+ replacement: match[0],
26026
+ twin: match[0]
25691
26027
  });
25692
26028
  continue;
25693
26029
  }
25694
26030
  let replacement;
25695
- if (varNameStr in variables) replacement = String(variables[varNameStr]);
25696
- else {
26031
+ let twinReplacement;
26032
+ if (varNameStr in variables) {
26033
+ replacement = String(variables[varNameStr]);
26034
+ twinReplacement = varNameStr in variableTwins ? variableTwins[varNameStr] : this.productLogTwin(variables[varNameStr], context);
26035
+ } else {
25697
26036
  const pseudoValue = await this.resolvePseudoParameter(varNameStr, context);
25698
26037
  if (pseudoValue !== void 0) replacement = String(pseudoValue);
25699
26038
  else try {
25700
26039
  const value = await this.resolveRef(varNameStr, context);
25701
26040
  replacement = String(value);
26041
+ twinReplacement = this.productLogTwin(value, context);
25702
26042
  } catch (refError) {
25703
26043
  if (varNameStr.includes(".")) try {
25704
26044
  const value = await this.resolveGetAtt(varNameStr, context);
25705
26045
  replacement = String(value);
26046
+ twinReplacement = this.productLogTwin(value, context);
25706
26047
  } catch (getAttError) {
25707
26048
  if (getAttError instanceof IntrinsicResolutionRefusalError) throw getAttError;
25708
26049
  this.rethrowStructuralSubFailure(varNameStr, getAttError, context);
@@ -25719,7 +26060,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25719
26060
  }
25720
26061
  replacements.push({
25721
26062
  match: match[0],
25722
- replacement
26063
+ replacement,
26064
+ twin: twinReplacement ?? replacement
25723
26065
  });
25724
26066
  }
25725
26067
  let cursor = 0;
@@ -25727,8 +26069,14 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25727
26069
  const entry = replacements[cursor++];
25728
26070
  return entry ? entry.replacement : whole;
25729
26071
  });
25730
- if (result.includes("{{resolve:")) result = await this.resolveDynamicReferences(result, context);
25731
- this.logger.debug(`Resolved Fn::Sub: ${this.maskSecretsForLog(result, context)}`);
26072
+ let twinCursor = 0;
26073
+ let twin = template.replace(/\$\{(!)?([^}]*)\}/g, (whole) => {
26074
+ const entry = replacements[twinCursor++];
26075
+ return entry ? entry.twin : whole;
26076
+ });
26077
+ if (result.includes("{{resolve:")) ({result, twin} = await this.resolveDynamicReferencesWithLogTwin(result, twin, context));
26078
+ this.rememberLogTwin(context, result, twin);
26079
+ this.logger.debug(`Resolved Fn::Sub: ${this.maskSecretsForLog(this.logTwinText(result, twin, context), context)}`);
25732
26080
  return result;
25733
26081
  }
25734
26082
  /**
@@ -25890,7 +26238,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25890
26238
  throw markNonRetryable(new IntrinsicResolutionRefusalError(`Fn::Split: the value to split${sourceClause} must be a string, got ${resolvedValue === null ? "null" : typeof resolvedValue}. Fn::Split accepts only a string; check the value or the intrinsic that produced it.`));
25891
26239
  }
25892
26240
  const result = resolvedValue.split(delimiter);
25893
- this.logger.debug(`Resolved Fn::Split: split by "${delimiter}" -> ${JSON.stringify(this.maskValueLeaves(result, context))}`);
26241
+ const pieceTwins = this.splitLogTwins(resolvedValue, delimiter, result, context);
26242
+ this.logger.debug(`Resolved Fn::Split: split by "${delimiter}" -> ${JSON.stringify(this.maskValueLeaves(pieceTwins, context))}`);
25894
26243
  return result;
25895
26244
  }
25896
26245
  /**
@@ -26651,8 +27000,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26651
27000
  const resolvedValue = await this.resolveValue(value, context);
26652
27001
  if (typeof resolvedValue !== "string") throw new Error(`Fn::Base64: value must resolve to a string, got ${typeof resolvedValue}`);
26653
27002
  const result = Buffer.from(resolvedValue).toString("base64");
26654
- if (context.recordedSecretValues && this.maskSecretsForLog(resolvedValue, context) !== resolvedValue) recordMaskOnlyValue(context.recordedSecretValues, result);
26655
- this.logger.debug(`Resolved Fn::Base64: ${this.maskSecretsForLog(resolvedValue, context)} -> ${this.maskSecretsForLog(result, context)}`);
27003
+ const inputLogText = this.logTextOfLeaf(resolvedValue, context);
27004
+ if (context.recordedSecretValues && (inputLogText !== resolvedValue || this.maskSecretsForLog(resolvedValue, context) !== resolvedValue)) recordMaskOnlyValue(context.recordedSecretValues, result);
27005
+ this.logger.debug(`Resolved Fn::Base64: ${this.maskSecretsForLog(inputLogText, context)} -> ${this.maskSecretsForLog(inputLogText !== resolvedValue ? "***" : result, context)}`);
26656
27006
  return result;
26657
27007
  }
26658
27008
  /**
@@ -26757,6 +27107,155 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26757
27107
  return masked;
26758
27108
  }
26759
27109
  /**
27110
+ * Is `value` WHOLE a secret either bag holds (issue #3100)? Asked of a
27111
+ * resolution PRODUCT only — never of a template literal — so an exact match
27112
+ * at any length is a verdict about what was written, not a floorless needle.
27113
+ */
27114
+ isRecordedSecretForLog(value, context) {
27115
+ return context?.recordedSecretValues?.has(value) === true || context?.inheritedSecrets?.has(value) === true;
27116
+ }
27117
+ /**
27118
+ * The text a `Resolved Fn::Join:` / `Resolved Fn::Sub:` line prints (issue
27119
+ * #3100): the twin through the needle mask — unless the needle mask ALSO
27120
+ * fires on the value itself, in which case the whole line is masked. The
27121
+ * twin splits the text at the spans it masked, so a 4+ character recorded
27122
+ * secret that overlaps one of them (or IS the whole value) is no longer a
27123
+ * contiguous needle in the twin, and masking the twin alone would print the
27124
+ * part of it outside the span. The value's own needle mask cannot be merged
27125
+ * with the twin's positions, so the line gives both up for `***`: never
27126
+ * more text than either mask alone would print. Returns the text BEFORE the
27127
+ * needle mask; each log line wraps it in `maskSecretsForLog` itself, so the
27128
+ * resolver's mask-coverage checker sees a masker at the site.
27129
+ */
27130
+ logTwinText(result, twin, context) {
27131
+ return twin !== result && this.maskSecretsForLog(result, context) !== result ? "***" : twin;
27132
+ }
27133
+ /**
27134
+ * The log twins of `Fn::Split`'s pieces (issue #3100), each registered for
27135
+ * the pass so an outer `Fn::Join` over the pieces keeps the mask. A source
27136
+ * that is itself a recorded or inherited secret counts as a twin of `***`;
27137
+ * a source with neither is its pieces' own twin.
27138
+ *
27139
+ * The source's twin is split by the same delimiter. When both splits give
27140
+ * the same number of pieces they are paired by position; when they do not —
27141
+ * a secret carrying the delimiter, or a delimiter that occurs in the mask
27142
+ * itself — no pairing can be trusted, so every piece is masked whole. Every
27143
+ * masked piece is registered by VALUE, so an equal string elsewhere in the
27144
+ * pass is masked too: the over-masking direction `logTwinOfProduct` accepts.
27145
+ */
27146
+ splitLogTwins(value, delimiter, pieces, context) {
27147
+ const bag = this.logTwinBag(context);
27148
+ const sourceTwin = this.isRecordedSecretForLog(value, context) ? "***" : bag ? LOG_TWINS_BY_PASS.get(bag)?.get(value) : void 0;
27149
+ if (sourceTwin === void 0) return pieces;
27150
+ const twinPieces = sourceTwin.split(delimiter);
27151
+ const aligned = twinPieces.length === pieces.length && !"***".includes(delimiter);
27152
+ return pieces.map((piece, index) => {
27153
+ const twin = this.logTwinText(piece, aligned ? twinPieces[index] ?? "***" : "***", context);
27154
+ this.rememberLogTwin(context, piece, twin);
27155
+ return twin;
27156
+ });
27157
+ }
27158
+ /**
27159
+ * The log twin of a resolution product as `String(value)` renders it (issue
27160
+ * #3100). A LIST is stringified element by element exactly as
27161
+ * `Array.prototype.join` does (`null` / `undefined` as the empty string,
27162
+ * nested lists recursively, comma-separated), so each element keeps its own
27163
+ * twin: `String()` over the whole list produced a string no registered twin
27164
+ * matched. No cycle guard: a product is JSON-sourced state or a fresh
27165
+ * resolution result, and neither can hold a self-referencing list.
27166
+ */
27167
+ productLogTwin(value, context) {
27168
+ if (Array.isArray(value)) return value.map((element) => element === null || element === void 0 ? "" : this.productLogTwin(element, context)).join(",");
27169
+ const text = String(value);
27170
+ return this.logTwinOfProduct({
27171
+ result: text,
27172
+ twin: text
27173
+ }, context).twin;
27174
+ }
27175
+ /**
27176
+ * The pre-mask log text of a string LEAF a debug line prints (issue #3100):
27177
+ * the leaf treated as a resolution product — masked whole when it is a
27178
+ * recorded secret, otherwise the masked twin this pass registered for it —
27179
+ * and then `logTwinText`'s whole-line guard. The caller still wraps the
27180
+ * result in `maskSecretsForLog`. Shared by every caller of `maskValueLeaves`
27181
+ * (the `Fn::Select`, `Fn::Split`, `Fn::Equals`, `Fn::FindInMap`,
27182
+ * `Fn::GetAZs` and `Fn::Cidr` lines and the VPC `Ipv6CidrBlocks` line among
27183
+ * them) and by the `Fn::Base64` line, so a string one
27184
+ * write already masked by position is not printed in the clear by the next
27185
+ * intrinsic that logs it. `maskValueLeaves` also masks values interpolated
27186
+ * into THROWN messages (the `Fn::Cidr` argument refusals, for one), so those
27187
+ * messages take the same position mask: a short secret an earlier write put
27188
+ * into the value is `***` there too, the direction the mask at the throw
27189
+ * already takes for a whole secret.
27190
+ */
27191
+ logTextOfLeaf(value, context) {
27192
+ return this.logTwinText(value, this.logTwinOfProduct({
27193
+ result: value,
27194
+ twin: value
27195
+ }, context).twin, context);
27196
+ }
27197
+ /** The bag a pass's log twins are keyed by (issue #3100): the recorded one, else the inherited one. */
27198
+ logTwinBag(context) {
27199
+ return context?.recordedSecretValues ?? context?.inheritedSecrets;
27200
+ }
27201
+ /**
27202
+ * The log twin of a RESOLUTION PRODUCT — an intrinsic part, variable or
27203
+ * placeholder — for issue #3100. Masked whole when its value is a recorded
27204
+ * secret. Otherwise a part that carries no mask of its own takes the masked
27205
+ * twin an earlier write of this pass registered for that exact string
27206
+ * (`rememberLogTwin`) — how a Join part that is itself an `Fn::Sub`, or a
27207
+ * `Fn::Select` over a reference-bearing string, keeps the inner mask. A part
27208
+ * whose OWN masked twin disagrees with the registered one (a list element
27209
+ * still spelling a reference) is masked whole, since the two span sets
27210
+ * cannot be merged; one that agrees keeps its twin.
27211
+ *
27212
+ * The registry is keyed by VALUE, so a product equal to a registered string
27213
+ * from another provenance takes its mask too. That over-masks a line whose
27214
+ * text holds a recorded secret at a position some write in this pass put
27215
+ * one. The registry holds masked twins only, so a lookup never unmasks.
27216
+ *
27217
+ * The whole-secret check reads the bags as they stand when the product is
27218
+ * placed, before the enclosing Join / Sub runs its final substitution. A
27219
+ * secret that substitution records is therefore not matched against an
27220
+ * earlier product equal to it. Such a product did not come from that secret:
27221
+ * a parameter holding one arrives in the inherited bag before its `Ref`
27222
+ * resolves, so what stays printed is a public value that coincides with it.
27223
+ */
27224
+ logTwinOfProduct(part, context) {
27225
+ if (this.isRecordedSecretForLog(part.result, context)) return {
27226
+ result: part.result,
27227
+ twin: "***"
27228
+ };
27229
+ const bag = this.logTwinBag(context);
27230
+ const registered = bag ? LOG_TWINS_BY_PASS.get(bag)?.get(part.result) : void 0;
27231
+ if (registered === void 0 || registered === part.twin) return part;
27232
+ return {
27233
+ result: part.result,
27234
+ twin: part.twin === part.result ? registered : "***"
27235
+ };
27236
+ }
27237
+ /**
27238
+ * Register a MASKED `twin` as the log twin of `result` for this pass (issue
27239
+ * #3100). An unmasked twin is not registered, so a later literal that
27240
+ * resolves to the same string cannot replace an earlier write's mask, and
27241
+ * two DIFFERENT masked twins for one string register `***` for the whole
27242
+ * string, since their spans cannot be merged. Scoped to the pass's bag
27243
+ * through a `WeakMap`, like the cross-stack associations, so it dies with
27244
+ * the bag and another pass's strings cannot be reached.
27245
+ */
27246
+ rememberLogTwin(context, result, twin) {
27247
+ if (twin === result) return;
27248
+ const bag = this.logTwinBag(context);
27249
+ if (!bag) return;
27250
+ let twins = LOG_TWINS_BY_PASS.get(bag);
27251
+ if (!twins) {
27252
+ twins = /* @__PURE__ */ new Map();
27253
+ LOG_TWINS_BY_PASS.set(bag, twins);
27254
+ }
27255
+ const existing = twins.get(result);
27256
+ twins.set(result, existing === void 0 || existing === twin ? twin : "***");
27257
+ }
27258
+ /**
26760
27259
  * A copy of `value` with every string LEAF (and every object KEY) masked, for
26761
27260
  * a caller about to ENCODE it into a message (issue
26762
27261
  * [#2759](https://github.com/go-to-k/cdkd/issues/2759)).
@@ -26788,7 +27287,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26788
27287
  maskValueLeaves(value, context) {
26789
27288
  const done = /* @__PURE__ */ new Map();
26790
27289
  const walk = (node) => {
26791
- if (typeof node === "string") return this.maskSecretsForLog(node, context);
27290
+ if (typeof node === "string") return this.maskSecretsForLog(this.logTextOfLeaf(node, context), context);
26792
27291
  if (node === null || typeof node !== "object") return node;
26793
27292
  const memo = done.get(node);
26794
27293
  if (memo !== void 0) return memo;
@@ -26800,7 +27299,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26800
27299
  }
26801
27300
  const out = Object.create(null);
26802
27301
  done.set(node, out);
26803
- for (const [key, child] of Object.entries(node)) out[this.maskSecretsForLog(key, context)] = walk(child);
27302
+ for (const [key, child] of Object.entries(node)) out[this.maskSecretsForLog(this.logTextOfLeaf(key, context), context)] = walk(child);
26804
27303
  return out;
26805
27304
  };
26806
27305
  return walk(value);
@@ -26846,8 +27345,32 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26846
27345
  return this.maskSecretsForLog(stripControlChars(this.maskSecretsForLog(value, context)), context);
26847
27346
  }
26848
27347
  async resolveDynamicReferences(value, context) {
27348
+ return (await this.resolveDynamicReferencesWithLogTwin(value, value, context)).result;
27349
+ }
27350
+ /**
27351
+ * {@link resolveDynamicReferences}, also returning the LOG TWIN of the result
27352
+ * (issue [#3100](https://github.com/go-to-k/cdkd/issues/3100), see
27353
+ * `LogTwin`). `logTwin` is the caller's twin of `value`, which may already
27354
+ * mask spans an earlier write put there.
27355
+ *
27356
+ * Every arm that writes into `result` writes into the twin too, replacing
27357
+ * the SAME token: a secret verdict writes {@link SECRET_MASK}, anything else
27358
+ * the value itself. The verdict is the one each arm already holds — the
27359
+ * fresh lookup's `isSecret`, the cache entry's own `secret` (never the
27360
+ * process-wide store, for the #1933 reason the cache-hit arm states), and
27361
+ * for a region-pinned sibling the twin the sibling itself returns. An arm
27362
+ * that `continue`s without writing leaves the token in both.
27363
+ *
27364
+ * A token whose text an earlier write masked (`{{resolve:${Pw}}}` over a
27365
+ * secret `Pw`) is not found in the twin, so the twin keeps the masked token
27366
+ * where the value holds its resolution. What the twin cannot carry is a
27367
+ * NEEDLE match that straddles a masked span, which is why the log lines go
27368
+ * through `logTwinText` rather than masking the twin alone.
27369
+ */
27370
+ async resolveDynamicReferencesWithLogTwin(value, logTwin, context) {
26849
27371
  const pattern = /\{\{resolve:([^}]+)\}\}/g;
26850
27372
  let result = value;
27373
+ let twin = logTwin;
26851
27374
  let match;
26852
27375
  const matches = [];
26853
27376
  while ((match = pattern.exec(value)) !== null) matches.push({
@@ -26861,8 +27384,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26861
27384
  const regionVerdict = classifyReplaySecretRegion(fullMatch, this.explicitRegion ?? this.resolverRegion, context?.producerRegions);
26862
27385
  if (regionVerdict.kind === "ambiguous") throw markNonRetryable(new DynamicReferenceRegionAmbiguousError(`Refusing to resolve the secret reference ${this.maskSecretsForLog(fullMatch, context)}: it names '${this.maskSecretsForLog(regionVerdict.secretName, context)}' without a region, and this stack reads from ${regionVerdict.foreignProducerRegions.join(", ")} as well as its own region. cdkd cannot tell which one must answer, and resolving against the wrong one yields a different secret. Spell the reference as a full ARN to say which region owns it.`));
26863
27386
  if (regionVerdict.kind === "named-region") {
26864
- const foreign = await this.resolverForProducerRegion(regionVerdict.region).resolveDynamicReferences(fullMatch, withoutProducerRegions(context));
26865
- result = result.replace(fullMatch, () => foreign);
27387
+ const foreign = await this.resolverForProducerRegion(regionVerdict.region).resolveDynamicReferencesWithLogTwin(fullMatch, fullMatch, withoutProducerRegions(context));
27388
+ result = result.replace(fullMatch, () => foreign.result);
27389
+ twin = twin.replace(fullMatch, () => foreign.twin);
26866
27390
  continue;
26867
27391
  }
26868
27392
  const cached = this.cachedDynamicReferences.get(fullMatch);
@@ -26872,6 +27396,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26872
27396
  if (context?.recordedSecretValues) recordResolvedPair(context.recordedSecretValues, fullMatch, cached.value);
26873
27397
  }
26874
27398
  result = result.replace(fullMatch, () => cached.value);
27399
+ twin = twin.replace(fullMatch, () => cached.secret && cached.value ? "***" : cached.value);
26875
27400
  continue;
26876
27401
  }
26877
27402
  const parts = inner.split(":");
@@ -26921,8 +27446,13 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
26921
27446
  if (service === "secretsmanager" || service === "ssm-secure") this.pinSecretVerdict(fullMatch, true);
26922
27447
  }
26923
27448
  result = result.replace(fullMatch, () => resolved);
27449
+ twin = twin.replace(fullMatch, () => isSecret && resolved ? "***" : resolved);
26924
27450
  }
26925
- return result;
27451
+ if (context) this.rememberLogTwin(context, result, twin);
27452
+ return {
27453
+ result,
27454
+ twin
27455
+ };
26926
27456
  }
26927
27457
  /**
26928
27458
  * Resolve a Secrets Manager dynamic reference
@@ -28486,7 +29016,7 @@ var CloudControlProvider = class {
28486
29016
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
28487
29017
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
28488
29018
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
28489
- const { ASGProvider } = await import("./asg-provider-BsC_XndT.js").then((n) => n.n);
29019
+ const { ASGProvider } = await import("./asg-provider-NffO_bpL.js").then((n) => n.n);
28490
29020
  const asgProvider = new ASGProvider();
28491
29021
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
28492
29022
  }
@@ -33684,12 +34214,20 @@ const WARM_THROUGHPUT_MEMBERS = ["ReadUnitsPerSecond", "WriteUnitsPerSecond"];
33684
34214
  *
33685
34215
  * The accepted STRING set is `Number()`'s, which is WIDER than a decimal
33686
34216
  * integer: `'0x1e'`, `'0o36'`, `'1e3'`, `'30.5'` and `' 30 '` all coerce.
33687
- * Whether CloudFormation accepts those for an `Integer`-typed property is
33688
- * unmeasured, and issue [#2698](https://github.com/go-to-k/cdkd/issues/2698)
33689
- * holds the live A/B that would settle it. It matters at the CALLERS that
33690
- * FORWARD the result to AWS rather than merely compare it — narrowing this
33691
- * shared helper would change the DynamoDB capacity readers too, so read that
33692
- * issue before tightening anything here.
34217
+ * CloudFormation accepts only the LAST of those for an `Integer`-typed
34218
+ * property — measured on `AWS::Logs::LogGroup.RetentionInDays`, issue
34219
+ * [#2698](https://github.com/go-to-k/cdkd/issues/2698) — and
34220
+ * {@link toCfnInteger} is the reader that matches that measurement. It matters
34221
+ * at the CALLERS that FORWARD the result to AWS rather than merely compare it:
34222
+ * the log-group provider switched; the DynamoDB capacity / throughput
34223
+ * forwarders (`coerceWarmThroughput` below, and the `GlobalTable` provider's
34224
+ * `ProvisionedThroughput` / `OnDemandThroughput` readers) still read through
34225
+ * THIS helper, because the measurement covers one resource handler and
34226
+ * DynamoDB's has not been A/B'd — issue
34227
+ * [#3135](https://github.com/go-to-k/cdkd/issues/3135) holds that pass. A
34228
+ * GUARD caller (`stateful-types.ts`'s `has-retention`, the DynamoDB
34229
+ * already-matches / decrease tests) is safe on the wider set: over-acceptance
34230
+ * there can only produce MORE refusals or skip fewer calls.
33693
34231
  *
33694
34232
  * EXPORTED, and not because warm throughput needs it exported. This exact rule
33695
34233
  * was hand-written three times — here, as `dynamodb-table-provider.ts`'s
@@ -33710,6 +34248,59 @@ function toFiniteNumber(value) {
33710
34248
  }
33711
34249
  }
33712
34250
  /**
34251
+ * A CloudFormation-borne `Integer` property, read the way CloudFormation reads
34252
+ * it, or `undefined` when CloudFormation would REJECT the value.
34253
+ *
34254
+ * The sibling of {@link toFiniteNumber} for the callers that FORWARD the number
34255
+ * to AWS. That helper is `Number()`, which accepts hex, octal, binary,
34256
+ * exponent and decimal-point spellings; CloudFormation does not, and a
34257
+ * provider forwarding `Number('0x1e')` sends `30` for a template CloudFormation
34258
+ * would have refused outright. Measured live on
34259
+ * `AWS::Logs::LogGroup.RetentionInDays` (us-east-1, 2026-09-14, issue
34260
+ * [#2698](https://github.com/go-to-k/cdkd/issues/2698); a one-resource stack
34261
+ * updated per spelling, live value read back with `logs describe-log-groups`):
34262
+ *
34263
+ * ```
34264
+ * template spelling CloudFormation live retention after
34265
+ * 30 (JSON number) accepted 30
34266
+ * "60" accepted 60
34267
+ * "+30" accepted 30
34268
+ * " 30 " (padded) accepted 30
34269
+ * " " (whitespace only) accepted, treated as ABSENT none (policy REMOVED)
34270
+ * "" (empty string) accepted, treated as ABSENT none (policy REMOVED)
34271
+ * property absent accepted none (policy REMOVED)
34272
+ * "0x1e" REJECTED (handler) unchanged
34273
+ * "1e3" REJECTED (handler) unchanged
34274
+ * "30.5" REJECTED (handler) unchanged
34275
+ * "30.0" REJECTED (handler) unchanged
34276
+ * "abc" REJECTED (handler) unchanged
34277
+ * true / false REJECTED (handler) unchanged
34278
+ * 0 / "0" REJECTED (handler: enum) unchanged
34279
+ * null REJECTED at update-stack unchanged
34280
+ * ```
34281
+ *
34282
+ * So the accepted string grammar is `optional sign + decimal digits`, with
34283
+ * surrounding whitespace TRIMMED — `/^\s*[+-]?\d+\s*$/`, then `Number()`. The
34284
+ * digits grammar is `config-shape.ts`'s {@link coerceCfnInteger}, reused rather
34285
+ * than respelled so the two cannot drift on it; the ONE difference is the
34286
+ * trim, and it is a measured difference, not a relaxation: that helper's own
34287
+ * measurement (a NESTED `GenerateSecretString.PasswordLength`, 2026-09-13)
34288
+ * saw `" 12 "` REJECTED where this one saw `" 30 "` accepted. Per-handler
34289
+ * behaviour varies on padding, so each helper carries its own measurement and
34290
+ * a caller picks by the property it forwards.
34291
+ *
34292
+ * What this helper does NOT decide: the empty / whitespace-only strings above
34293
+ * are CloudFormation's spelling of "property absent", which is a statement
34294
+ * about the PROPERTY, not about the number — they answer `undefined` here, and
34295
+ * the caller decides whether `undefined` means "absent" or "unusable" from the
34296
+ * raw value (`logs-loggroup-provider.ts`'s `assertUsableRetention` is the
34297
+ * reference). `0` / `'0'` parse to `0` here; whether zero is usable is again the
34298
+ * property's question (the log-group handler rejects it by enum).
34299
+ */
34300
+ function toCfnInteger(value) {
34301
+ return coerceCfnInteger(typeof value === "string" ? value.trim() : value);
34302
+ }
34303
+ /**
33713
34304
  * Coerce a CFn `WarmThroughput` block to the numeric shape the SDK's `Long`
33714
34305
  * fields accept, PER MEMBER.
33715
34306
  *
@@ -36008,6 +36599,45 @@ function effectiveProvisionedBy(record, fallbackProvisionedBy) {
36008
36599
  return record?.provisionedBy ?? fallbackProvisionedBy;
36009
36600
  }
36010
36601
  /**
36602
+ * One spelling of "this value came from a rollback-journal record, and is
36603
+ * about to be interpolated into a message a terminal will render" (issue
36604
+ * #3092). The journal is a sibling of `state.json` in the same bucket,
36605
+ * writable by anyone with `s3:PutObject` and validated no more than an
36606
+ * unchecked cast; cdkd's output is line-oriented, so an injected newline
36607
+ * invents a line that reads like a real one. This executor runs under the
36608
+ * standalone `cdkd rollback` AND under the deploy engine's automatic
36609
+ * rollback, so its lines print on every failed deploy.
36610
+ *
36611
+ * `displayIdent`: the ASCII allowlist with `UNRENDERABLE` for a value that
36612
+ * sanitizes to nothing, a length cap, and a visible boundary (a JSON-quoted
36613
+ * rendering) for a value that is not a plain identifier -- the all-ASCII
36614
+ * `X (AWS::RDS::DBInstance) -- already reverted` the allowlist lets through.
36615
+ * A logical id, a CFn resource type and a change type all have known
36616
+ * charsets. Call it for those; `grep safe(` answers the scope.
36617
+ *
36618
+ * NOT for a value that is USED rather than shown -- the ids passed to a
36619
+ * provider call, the keys into `stateResources`, the `msg` a classifier reads.
36620
+ * NOT for the `reason` / `survivorReason` strings handed to `ctx.recordEvent`:
36621
+ * those are PERSISTED into `deployments/*.jsonl` raw and `cdkd events`
36622
+ * sanitizes them on the way out, so sanitizing here would put a display
36623
+ * transform on a stored value and double it at render (the go-to-k/cdkd#2170
36624
+ * direction). NOT for free-form error text either: an SDK message legitimately
36625
+ * carries non-ASCII, so a site rendering one calls `displaySafe()` directly and
36626
+ * takes the DENYLIST, as `formatError` does for a `cause`.
36627
+ */
36628
+ function safe(value) {
36629
+ return displayIdent(value);
36630
+ }
36631
+ /**
36632
+ * The one shape of `op.logicalId` this executor will print INSIDE a command it
36633
+ * invites the user to paste (`cdkd rollback --orphan <id>`): CloudFormation's
36634
+ * own logical-id charset and length. Stricter than "`safe()` is the identity
36635
+ * on it" on purpose -- `~user` and `=x` are plain identifiers the shell
36636
+ * expands before cdkd sees them (issue #3092 review). Not a display rule: a
36637
+ * legitimate id the executor merely SHOWS still goes through `safe()`.
36638
+ */
36639
+ const PASTEABLE_LOGICAL_ID = /^[A-Za-z0-9]{1,255}$/;
36640
+ /**
36011
36641
  * `UpdateReplacePolicy: Snapshot` on a rollback's delete-of-the-NEW-resource
36012
36642
  * (issue #1354): honor it where it costs nothing — an atomic-final-snapshot
36013
36643
  * type on the SDK route gets a generated identifier threaded into the delete
@@ -36104,7 +36734,7 @@ function rollbackRetainsNewResource(record) {
36104
36734
  */
36105
36735
  function retainedSurvivorMessages(logicalId, resourceType, survivorPhysicalId, stateClause) {
36106
36736
  return {
36107
- warn: ` ⚠ ${logicalId} (${resourceType}) has UpdateReplacePolicy: Retain — the replacement's new physical resource (${survivorPhysicalId}) is RETAINED by this rollback and is no longer tracked by cdkd: it keeps running and incurring cost, and \`cdkd destroy\` will not remove it. Delete it yourself once you no longer need it. ${stateClause}`,
36737
+ warn: ` ⚠ ${safe(logicalId)} (${safe(resourceType)}) has UpdateReplacePolicy: Retain — the replacement's new physical resource (${displaySafe(survivorPhysicalId)}) is RETAINED by this rollback and is no longer tracked by cdkd: it keeps running and incurring cost, and \`cdkd destroy\` will not remove it. Delete it yourself once you no longer need it. ${displaySafe(stateClause)}`,
36108
36738
  reason: `UpdateReplacePolicy: Retain kept the replacement's new ${resourceType} (${survivorPhysicalId}); it is live, still billing, and no longer tracked by cdkd. ${stateClause}`
36109
36739
  };
36110
36740
  }
@@ -36655,7 +37285,7 @@ function redactRollbackRecord(record, secrets, journaledProps) {
36655
37285
  }
36656
37286
  async function updateWithRollbackRetry(provider, args, logicalId, logger, isInterrupted, secrets) {
36657
37287
  if (provider.disableOuterRetry) return await withCurrentResourceSecrets(secrets, () => provider.update(...args));
36658
- return await withRetry(() => withCurrentResourceSecrets(secrets, () => provider.update(...args)), logicalId, {
37288
+ return await withRetry(() => withCurrentResourceSecrets(secrets, () => provider.update(...args)), safe(logicalId), {
36659
37289
  logger: maskingRetryLogger(logger, secrets),
36660
37290
  ...isInterrupted && {
36661
37291
  isInterrupted,
@@ -36747,13 +37377,14 @@ async function updateWithRollbackRetry(provider, args, logicalId, logger, isInte
36747
37377
  async function createWithRollbackRetry(provider, create, logicalId, logger, isInterrupted, secrets, outer) {
36748
37378
  if (provider.disableOuterRetry) return await create();
36749
37379
  const maskedLogger = maskingRetryLogger(logger, secrets);
36750
- return await withRetry(() => withRetry(create, logicalId, {
37380
+ const shownId = safe(logicalId);
37381
+ return await withRetry(() => withRetry(create, shownId, {
36751
37382
  logger: maskedLogger,
36752
37383
  ...isInterrupted && {
36753
37384
  isInterrupted,
36754
37385
  onInterrupted: () => /* @__PURE__ */ new Error("Rollback interrupted while retrying the replay re-create")
36755
37386
  }
36756
- }), logicalId, {
37387
+ }), shownId, {
36757
37388
  ...RECREATE_RETRY_SCHEDULE,
36758
37389
  logger: maskedLogger,
36759
37390
  ...isInterrupted && {
@@ -36836,18 +37467,18 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36836
37467
  try {
36837
37468
  switch (action) {
36838
37469
  case "unrecoverable-delete":
36839
- logger.warn(` Rollback: Cannot restore deleted resource ${op.logicalId} (${op.resourceType}) — resource has already been deleted`);
37470
+ logger.warn(` Rollback: Cannot restore deleted resource ${safe(op.logicalId)} (${safe(op.resourceType)}) — resource has already been deleted`);
36840
37471
  result.warnings++;
36841
37472
  return;
36842
37473
  case "skip-already-done":
36843
- logger.debug(` Rollback: ${op.logicalId} already reverted, skipping`);
37474
+ logger.debug(` Rollback: ${safe(op.logicalId)} already reverted, skipping`);
36844
37475
  return;
36845
37476
  case "skip-mismatch":
36846
- logger.warn(` Rollback: Skipping ${op.logicalId} — its physical id changed since the failed deploy (replaced by a later attempt); manual attention may be required`);
37477
+ logger.warn(` Rollback: Skipping ${safe(op.logicalId)} — its physical id changed since the failed deploy (replaced by a later attempt); manual attention may be required`);
36847
37478
  result.warnings++;
36848
37479
  return;
36849
37480
  case "skip-absent":
36850
- logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource no longer in state, skipping`);
37481
+ logger.warn(` Rollback: Cannot restore ${safe(op.logicalId)} — resource no longer in state, skipping`);
36851
37482
  result.warnings++;
36852
37483
  return;
36853
37484
  case "orphan-flag":
@@ -36856,7 +37487,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36856
37487
  const orphanFlagProvisionedBy = effectiveProvisionedBy(record, op.provisionedBy);
36857
37488
  createRollbackRoute = orphanFlagProvisionedBy;
36858
37489
  delete stateResources[op.logicalId];
36859
- logger.info(` Rollback: Orphaning created resource ${op.logicalId} (--orphan)`);
37490
+ logger.info(` Rollback: Orphaning created resource ${safe(op.logicalId)} (--orphan)`);
36860
37491
  await afterOp?.(op.logicalId);
36861
37492
  ctx.recordEvent?.({
36862
37493
  eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
@@ -36870,7 +37501,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36870
37501
  reason: `--orphan left ${op.logicalId} (${op.resourceType}) in AWS as ${record.physicalId} and dropped it from state; it is live, still billing, and no longer tracked by cdkd.`
36871
37502
  }
36872
37503
  });
36873
- } else logger.info(` Rollback: Leaving ${op.logicalId} at its new state (--orphan)`);
37504
+ } else logger.info(` Rollback: Leaving ${safe(op.logicalId)} at its new state (--orphan)`);
36874
37505
  return;
36875
37506
  case "orphan-retain": {
36876
37507
  const record = stateResources[op.logicalId];
@@ -36886,7 +37517,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36886
37517
  onOrphan?.(orphaned);
36887
37518
  }
36888
37519
  delete stateResources[op.logicalId];
36889
- logger.info(` Rollback: Leaving ${op.logicalId} (${op.resourceType}) in AWS (DeletionPolicy: Retain) — removed from state`);
37520
+ logger.info(` Rollback: Leaving ${safe(op.logicalId)} (${safe(op.resourceType)}) in AWS (DeletionPolicy: Retain) — removed from state`);
36890
37521
  await afterOp?.(op.logicalId);
36891
37522
  ctx.recordEvent?.({
36892
37523
  eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
@@ -36905,7 +37536,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36905
37536
  case "delete":
36906
37537
  case "delete-with-final-snapshot": {
36907
37538
  if (!op.physicalId) {
36908
- logger.warn(` Rollback: Cannot delete ${op.logicalId} — no physical ID recorded`);
37539
+ logger.warn(` Rollback: Cannot delete ${safe(op.logicalId)} — no physical ID recorded`);
36909
37540
  result.warnings++;
36910
37541
  return;
36911
37542
  }
@@ -36915,7 +37546,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36915
37546
  const takeFinalSnapshot = snapshotPolicy && ctx.skipFinalSnapshot !== true;
36916
37547
  let finalSnapshotIdentifier;
36917
37548
  if (takeFinalSnapshot) finalSnapshotIdentifier = await prepareCreateRollbackFinalSnapshot(op, deleteProvisionedBy, ctx);
36918
- logger.info(` Rollback: Deleting created resource ${op.logicalId} (${op.resourceType})` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
37549
+ logger.info(` Rollback: Deleting created resource ${safe(op.logicalId)} (${safe(op.resourceType)})` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
36919
37550
  const { provider } = ctx.providerRegistry.getProviderFor({
36920
37551
  resourceType: op.resourceType,
36921
37552
  provisionedBy: deleteProvisionedBy
@@ -36925,7 +37556,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36925
37556
  ...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
36926
37557
  }), op.logicalId, op.physicalId, "while rolling back its CREATE");
36927
37558
  delete stateResources[op.logicalId];
36928
- logger.info(` Rollback: ${op.logicalId} deleted successfully`);
37559
+ logger.info(` Rollback: ${safe(op.logicalId)} deleted successfully`);
36929
37560
  await afterOp?.(op.logicalId);
36930
37561
  ctx.recordEvent?.({
36931
37562
  eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
@@ -36940,7 +37571,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36940
37571
  case "reverse-replacement-readopt": {
36941
37572
  const current = stateResources[op.logicalId];
36942
37573
  const prev = op.previousState;
36943
- logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — deleting the new resource and re-adopting the retained old one (${prev.physicalId})`);
37574
+ logger.info(` Rollback: Reversing replacement of ${safe(op.logicalId)} (${safe(op.resourceType)}) — deleting the new resource and re-adopting the retained old one (${displaySafe(prev.physicalId)})`);
36944
37575
  /**
36945
37576
  * Set when this arm ORPHANS the replacement's new copy. Read at the
36946
37577
  * `ROLLBACK_RESOURCE_SUCCEEDED` event below, which is the only channel
@@ -36974,7 +37605,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36974
37605
  }), op.logicalId, current.physicalId, "while reversing its replacement (re-adopting the retained old resource)");
36975
37606
  }
36976
37607
  stateResources[op.logicalId] = prev;
36977
- logger.info(` Rollback: ${op.logicalId} restored to the retained old resource`);
37608
+ logger.info(` Rollback: ${safe(op.logicalId)} restored to the retained old resource`);
36978
37609
  await afterOp?.(op.logicalId);
36979
37610
  const survivorProvisionedBy = current.provisionedBy;
36980
37611
  ctx.recordEvent?.({
@@ -36998,8 +37629,8 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
36998
37629
  const resolvedPrevProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId) ?? {};
36999
37630
  refuseMaskedReplayBaseline(resolvedPrevProps, op.logicalId);
37000
37631
  recordNestedStackParameterExpressions(secrets, op.resourceType, resolvedPrevProps, prev.properties, STATE_DERIVED_RULES);
37001
- logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
37002
- if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}) is a stateful type — the old physical resource's data was destroyed by the replacement and is NOT recovered by this rollback; the re-created resource starts empty.`);
37632
+ logger.info(` Rollback: Reversing replacement of ${safe(op.logicalId)} (${safe(op.resourceType)}) — re-creating the old resource and deleting the new one`);
37633
+ if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${safe(op.logicalId)} (${safe(op.resourceType)}) is a stateful type — the old physical resource's data was destroyed by the replacement and is NOT recovered by this rollback; the re-created resource starts empty.`);
37003
37634
  const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
37004
37635
  resourceType: op.resourceType,
37005
37636
  provisionedBy: prev.provisionedBy
@@ -37018,8 +37649,8 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
37018
37649
  } catch (createError) {
37019
37650
  const msg = createError instanceof Error ? createError.message : String(createError);
37020
37651
  if (!isNameCollisionError(msg)) throw createError;
37021
- if (rollbackRetainsNewResource(current)) throw markNonRetryable(new CdkdError(maskSecretsInText(`Cannot reverse the replacement of ${op.logicalId} (${op.resourceType}): the re-create of the old resource (${prev.physicalId}) collided with the name still held by the new one (${current.physicalId}), and UpdateReplacePolicy: Retain pins that new resource in place, so cdkd will not delete it to free the name. Delete the new resource yourself, or remove UpdateReplacePolicy: Retain, then re-run \`cdkd rollback\` — the journal is kept, so the revert resumes from here. To leave THIS resource alone and let the rest of the rollback proceed, re-run with \`cdkd rollback --orphan ${op.logicalId}\`: one op failure stops the segment loop, so a single pinned resource otherwise halts every OLDER segment too. Underlying collision: ${msg}`, secrets), "NAMED_REPLACEMENT_COLLISION", maskSecretsInError(createError instanceof Error ? createError : void 0, secrets)));
37022
- logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${current.physicalId}) first...`);
37652
+ if (rollbackRetainsNewResource(current)) throw markNonRetryable(new CdkdError(maskSecretsInText(`Cannot reverse the replacement of ${safe(op.logicalId)} (${safe(op.resourceType)}): the re-create of the old resource (${safe(prev.physicalId)}) collided with the name still held by the new one (${safe(current.physicalId)}), and UpdateReplacePolicy: Retain pins that new resource in place, so cdkd will not delete it to free the name. Delete the new resource yourself, or remove UpdateReplacePolicy: Retain, then re-run \`cdkd rollback\` — the journal is kept, so the revert resumes from here. To leave THIS resource alone and let the rest of the rollback proceed, re-run with ` + (typeof op.logicalId === "string" && PASTEABLE_LOGICAL_ID.test(op.logicalId) ? `\`cdkd rollback --orphan ${op.logicalId}\`: one op failure stops the ` : "`cdkd rollback --orphan <id>` (the id is withheld: it is not a plain CloudFormation logical id, so a pasted command could be reshaped by the shell or name a different resource -- read it from `cdkd events`): one op failure stops the ") + `segment loop, so a single pinned resource otherwise halts every OLDER segment too. Underlying collision: ${displaySafe(msg)}`, secrets), "NAMED_REPLACEMENT_COLLISION", maskSecretsInError(createError instanceof Error ? createError : void 0, secrets)));
37653
+ logger.info(` Rollback: re-create collided with the new resource's name — deleting the new resource (${displaySafe(current.physicalId)}) first...`);
37023
37654
  {
37024
37655
  const finalSnapshotIdentifier = rollbackFinalSnapshotId(op.resourceType, current, op.provisionedBy);
37025
37656
  throwIfDeleteSkipped(await resolveNewDeleteProvider().delete(op.logicalId, current.physicalId, op.resourceType, current.properties, {
@@ -37036,12 +37667,12 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
37036
37667
  interruptedMessage: "Rollback interrupted while waiting for the old name to release"
37037
37668
  });
37038
37669
  } catch (recreateError) {
37039
- throw new Error(maskSecretsInText(`Failed to re-create the old ${op.logicalId} after the new resource (${current.physicalId}) was already deleted: ${recreateError instanceof Error ? recreateError.message : String(recreateError)}. The resource is now absent — fix forward with 'cdkd deploy'.`, secrets), { cause: maskSecretsInError(recreateError instanceof Error ? recreateError : void 0, secrets) });
37670
+ throw new Error(maskSecretsInText(`Failed to re-create the old ${safe(op.logicalId)} after the new resource (${current.physicalId}) was already deleted: ${displaySafe(recreateError instanceof Error ? recreateError.message : String(recreateError))}. The resource is now absent — fix forward with 'cdkd deploy'.`, secrets), { cause: maskSecretsInError(recreateError instanceof Error ? recreateError : void 0, secrets) });
37040
37671
  }
37041
37672
  }
37042
37673
  const adoptedLiveNewResource = !deletedNewFirst && createResult.physicalId === current.physicalId;
37043
37674
  if (adoptedLiveNewResource) {
37044
- logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}): the re-create returned the LIVE new resource (${current.physicalId}) instead of re-creating the old one — its Create API is name-idempotent and the new resource still holds the same user-supplied name. Skipping the delete-new step (it would delete that very resource). The old resource's ORIGINAL properties may NOT have been re-applied; state now records the pre-replacement properties, so run 'cdkd drift ${stackName}' to inspect and 'cdkd deploy' to reconcile, or rename the resource to make the replacement reversible.`);
37675
+ logger.warn(` ⚠ ${safe(op.logicalId)} (${safe(op.resourceType)}): the re-create returned the LIVE new resource (${displaySafe(current.physicalId)}) instead of re-creating the old one — its Create API is name-idempotent and the new resource still holds the same user-supplied name. Skipping the delete-new step (it would delete that very resource). The old resource's ORIGINAL properties may NOT have been re-applied; state now records the pre-replacement properties, so run 'cdkd drift ${stackName}' to inspect and 'cdkd deploy' to reconcile, or rename the resource to make the replacement reversible.`);
37045
37676
  result.warnings++;
37046
37677
  }
37047
37678
  const { observedProperties: _staleObserved, ...prevRecord } = prev;
@@ -37065,11 +37696,11 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
37065
37696
  ...finalSnapshotIdentifier !== void 0 && { finalSnapshotIdentifier }
37066
37697
  }), op.logicalId, current.physicalId, "while deleting the new resource after re-creating the old one");
37067
37698
  } catch (deleteError) {
37068
- logger.warn(maskSecretsInText(` Rollback: old ${op.logicalId} re-created, but deleting the new resource (${current.physicalId}) failed: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}. Delete it manually — it is no longer tracked in state.`, secrets));
37699
+ logger.warn(maskSecretsInText(` Rollback: old ${safe(op.logicalId)} re-created, but deleting the new resource (${displaySafe(current.physicalId)}) failed: ${displaySafe(deleteError instanceof Error ? deleteError.message : String(deleteError))}. Delete it manually — it is no longer tracked in state.`, secrets));
37069
37700
  survivorReason = `The replacement's new ${op.resourceType} (${current.physicalId}) could not be deleted after the old resource was re-created: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}. It is live, still billing, and no longer tracked by cdkd — delete it yourself.`;
37070
37701
  result.warnings++;
37071
37702
  }
37072
- logger.info(adoptedLiveNewResource ? ` Rollback: ${op.logicalId} adopted the live resource (${createResult.physicalId}) — replacement NOT fully reversed (name-idempotent Create API)` : ` Rollback: ${op.logicalId} replacement reversed (old resource re-created as ${createResult.physicalId})`);
37703
+ logger.info(adoptedLiveNewResource ? ` Rollback: ${safe(op.logicalId)} adopted the live resource (${createResult.physicalId}) — replacement NOT fully reversed (name-idempotent Create API)` : ` Rollback: ${safe(op.logicalId)} replacement reversed (old resource re-created as ${createResult.physicalId})`);
37073
37704
  const survivorProvisionedBy = current.provisionedBy;
37074
37705
  ctx.recordEvent?.({
37075
37706
  eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
@@ -37088,18 +37719,18 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
37088
37719
  }
37089
37720
  case "revert": {
37090
37721
  if (!op.previousState) {
37091
- logger.warn(` Rollback: Cannot restore ${op.logicalId} — no previous state available`);
37722
+ logger.warn(` Rollback: Cannot restore ${safe(op.logicalId)} — no previous state available`);
37092
37723
  result.warnings++;
37093
37724
  return;
37094
37725
  }
37095
37726
  const previousState = op.previousState;
37096
37727
  const current = stateResources[op.logicalId];
37097
37728
  if (!current) {
37098
- logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource not found in current state`);
37729
+ logger.warn(` Rollback: Cannot restore ${safe(op.logicalId)} — resource not found in current state`);
37099
37730
  result.warnings++;
37100
37731
  return;
37101
37732
  }
37102
- logger.info(` Rollback: Restoring ${op.logicalId} (${op.resourceType}) to previous state`);
37733
+ logger.info(` Rollback: Restoring ${safe(op.logicalId)} (${safe(op.resourceType)}) to previous state`);
37103
37734
  const { provider } = ctx.providerRegistry.getProviderFor({
37104
37735
  resourceType: op.resourceType,
37105
37736
  provisionedBy: op.provisionedBy
@@ -37121,8 +37752,8 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
37121
37752
  ], op.logicalId, logger, isInterrupted, secrets);
37122
37753
  stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(previousState, revertResult), secrets, previousState.properties);
37123
37754
  const rollbackPartial = updatePartialReason(revertResult);
37124
- if (rollbackPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${op.logicalId} restored, ${updatePartialMessage(rollbackPartial)}`, secrets));
37125
- else logger.info(` Rollback: ${op.logicalId} restored successfully`);
37755
+ if (rollbackPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${safe(op.logicalId)} restored, ${updatePartialMessage(rollbackPartial)}`, secrets));
37756
+ else logger.info(` Rollback: ${safe(op.logicalId)} restored successfully`);
37126
37757
  await afterOp?.(op.logicalId);
37127
37758
  ctx.recordEvent?.({
37128
37759
  eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
@@ -37137,7 +37768,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
37137
37768
  }
37138
37769
  }
37139
37770
  } catch (rollbackError) {
37140
- logger.warn(maskSecretsInText(` Rollback failed for ${op.logicalId} (${op.changeType}): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`, secrets));
37771
+ logger.warn(maskSecretsInText(` Rollback failed for ${safe(op.logicalId)} (${safe(op.changeType)}): ${displaySafe(rollbackError instanceof Error ? rollbackError.message : String(rollbackError))}`, secrets));
37141
37772
  logger.warn(" Continuing with remaining rollback operations...");
37142
37773
  result.failures++;
37143
37774
  const failedRoute = createRollbackRoute ?? op.provisionedBy;
@@ -37199,14 +37830,14 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
37199
37830
  try {
37200
37831
  switch (action) {
37201
37832
  case "skip-failed-noop":
37202
- logger.info(` Rollback: failed ${op.changeType} of ${op.logicalId} (${op.resourceType}) left nothing to revert, skipping`);
37833
+ logger.info(` Rollback: failed ${safe(op.changeType)} of ${safe(op.logicalId)} (${safe(op.resourceType)}) left nothing to revert, skipping`);
37203
37834
  break;
37204
37835
  case "skip-failed-unknown":
37205
- logger.warn(` Rollback: failed CREATE of ${op.logicalId} (${op.resourceType}) recorded no physical id — if it was partially created in AWS, delete it manually`);
37836
+ logger.warn(` Rollback: failed CREATE of ${safe(op.logicalId)} (${safe(op.resourceType)}) recorded no physical id — if it was partially created in AWS, delete it manually`);
37206
37837
  result.warnings++;
37207
37838
  break;
37208
37839
  case "skip-failed-absent":
37209
- logger.warn(` Rollback: cannot revert failed UPDATE of ${op.logicalId} — no previous state available, skipping`);
37840
+ logger.warn(` Rollback: cannot revert failed UPDATE of ${safe(op.logicalId)} — no previous state available, skipping`);
37210
37841
  result.warnings++;
37211
37842
  break;
37212
37843
  case "orphan-failed-create-retain": {
@@ -37223,7 +37854,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
37223
37854
  options.onOrphan?.(orphaned);
37224
37855
  }
37225
37856
  delete stateResources[op.logicalId];
37226
- logger.info(` Rollback: leaving partially-created ${op.logicalId} (${op.resourceType}) in AWS (DeletionPolicy: Retain) — removed from state`);
37857
+ logger.info(` Rollback: leaving partially-created ${safe(op.logicalId)} (${safe(op.resourceType)}) in AWS (DeletionPolicy: Retain) — removed from state`);
37227
37858
  await options.afterOp?.(op.logicalId);
37228
37859
  ctx.recordEvent?.({
37229
37860
  eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
@@ -37243,7 +37874,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
37243
37874
  const takeFinalSnapshot = snapshotPolicy && ctx.skipFinalSnapshot !== true;
37244
37875
  let finalSnapshotIdentifier;
37245
37876
  if (takeFinalSnapshot) finalSnapshotIdentifier = await prepareCreateRollbackFinalSnapshot(op, deleteProvisionedBy, ctx);
37246
- logger.info(` Rollback: deleting partially-created ${op.logicalId} (${op.resourceType}) (--revert-failed)` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
37877
+ logger.info(` Rollback: deleting partially-created ${safe(op.logicalId)} (${safe(op.resourceType)}) (--revert-failed)` + (takeFinalSnapshot ? " — DeletionPolicy: Snapshot" : "") + (snapshotPolicy && !takeFinalSnapshot ? " — DeletionPolicy: Snapshot NOT taken (--skip-final-snapshot)" : ""));
37247
37878
  const { provider } = ctx.providerRegistry.getProviderFor({
37248
37879
  resourceType: op.resourceType,
37249
37880
  provisionedBy: deleteProvisionedBy
@@ -37267,7 +37898,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
37267
37898
  case "revert-failed-update": {
37268
37899
  const current = stateResources[op.logicalId];
37269
37900
  const prev = op.previousState;
37270
- logger.info(` Rollback: force-reverting failed UPDATE of ${op.logicalId} (${op.resourceType}) to its pre-deploy properties (--revert-failed; remote state is unknown)`);
37901
+ logger.info(` Rollback: force-reverting failed UPDATE of ${safe(op.logicalId)} (${safe(op.resourceType)}) to its pre-deploy properties (--revert-failed; remote state is unknown)`);
37271
37902
  const { provider } = ctx.providerRegistry.getProviderFor({
37272
37903
  resourceType: op.resourceType,
37273
37904
  provisionedBy: op.provisionedBy ?? current.provisionedBy
@@ -37289,8 +37920,8 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
37289
37920
  ], op.logicalId, logger, options.isInterrupted, secrets);
37290
37921
  stateResources[op.logicalId] = redactRollbackRecord(recordAfterRollbackUpdate(prev, revertFailedResult), secrets, prev.properties);
37291
37922
  const revertFailedPartial = updatePartialReason(revertFailedResult);
37292
- if (revertFailedPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${op.logicalId} reverted, ${updatePartialMessage(revertFailedPartial)}`, secrets));
37293
- else logger.info(` Rollback: ${op.logicalId} reverted successfully`);
37923
+ if (revertFailedPartial !== void 0) logger.warn(maskSecretsInText(` Rollback: ${safe(op.logicalId)} reverted, ${updatePartialMessage(revertFailedPartial)}`, secrets));
37924
+ else logger.info(` Rollback: ${safe(op.logicalId)} reverted successfully`);
37294
37925
  await options.afterOp?.(op.logicalId);
37295
37926
  ctx.recordEvent?.({
37296
37927
  eventType: "ROLLBACK_RESOURCE_SUCCEEDED",
@@ -37305,7 +37936,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
37305
37936
  }
37306
37937
  }
37307
37938
  } catch (revertError) {
37308
- logger.warn(maskSecretsInText(` Rollback failed for failed-op ${op.logicalId} (${op.changeType}): ${revertError instanceof Error ? revertError.message : String(revertError)}`, secrets));
37939
+ logger.warn(maskSecretsInText(` Rollback failed for failed-op ${safe(op.logicalId)} (${safe(op.changeType)}): ${displaySafe(revertError instanceof Error ? revertError.message : String(revertError))}`, secrets));
37309
37940
  result.failures++;
37310
37941
  pending.add(op);
37311
37942
  const failedRoute = createRollbackRoute ?? op.provisionedBy;
@@ -41041,5 +41672,5 @@ var DeployEngine = class DeployEngine {
41041
41672
  };
41042
41673
 
41043
41674
  //#endregion
41044
- export { getCurrentResourceSecrets as $, validateAssetBucketName as $n, DependencyError as $r, findActionableSilentDrops as $t, planOrphanAdoption as A, CUSTOM_RESOURCE_RESPONSE_PREFIX as An, warnDeprecatedNoPrefixCliFlag as Ar, resolveExplicitPhysicalId as At, bold as B, WorkGraph as Bn, derivePartitionAndUrlSuffix as Br, requireConfigObject as Bt, createPreDeleteFinalSnapshot as C, markNonRetryable as Ci, S3StateBackend as Cn, resolveAutoAssetStorage as Cr, coerceParameterTypedValue as Ct, makeCanonicalizePropertiesFn as D, forceQuitRecoveryClause as Dn, resolveStateBucketWithDefaultAndSource as Dr, refStateLookupFromResource as Dt, unsupportedFinalSnapshotError as E, __exportAll as Ei, buildLockContentionMessage as En, resolveStateBucketWithDefault as Er, parameterTypeMayLoseSecretIdentity as Et, WARM_THROUGHPUT_MEMBERS as F, orphansAfterRollback as Fn, uploadCfnTemplate as Fr, configIntegerRefusal as Ft, yellow as G, AssetModeResolver as Gn, AwsClients as Gr, s3BucketArn as Gt, gray as H, createAssetRedirectResolver as Hn, processStackMessages as Hr, classifyReplaySecretRegion as Ht, coerceWarmThroughput as I, orphansCarriedFrom as In, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Ir, configStringRefusal as It, exportAliasCollisionScrubWarning as J, ensureAssetStorage as Jn, setAwsClients as Jr, s3BucketRegionalDomainName as Jt, collectDeclaredOutputNames as K, BOOTSTRAP_MARKER_PREFIX as Kn, getAwsClients as Kr, s3BucketDomainName as Kt, isWarmThroughputDecrease as L, shouldRetainResource as Ln, expectedOwnerParam as Lr, readConfigString as Lt, isStatefulRecreateTargetForReplace as M, exportNamesCarriedFrom as Mn, CFN_TEMPLATE_URL_LIMIT as Mr, coerceCfnBoolean as Mt, isStatefulRecreateTargetSync as N, importableOutputKeys as Nn, MIGRATE_TMP_PREFIX as Nr, coerceCfnInteger as Nt, extractDeploymentEventError as O, formatLockExpiry as On, resolveUseCdkBootstrapAssets as Or, WAFv2WebACLProvider as Ot, renderStatefulReason as P, importableOutputs as Pn, findLargeInlineResources as Pr, configBooleanRefusal as Pt, secretSafeKeyDisplay as Q, readBootstrapMarkerBody as Qn, CrossAccountSecretRefusalError as Qr, INTRINSIC_KEYS as Qt, toFiniteNumber as R, AssetPublisher as Rn, PARTITION_TABLE as Rr, replayWarn as Rt, ccRoutedFinalSnapshotError as S, isTransientServerError as Si, LockManager as Sn, resolveApp as Sr, cfnRefValueFromPhysicalId as St, refusesFinalSnapshot as T, retryClassificationText as Ti, buildForceUnlockCommand as Tn, resolveSkipPrefix as Tr, isUnboundTemplateParameter as Tt, green as U, loadPublishableAssetManifest as Un, clearBucketRegionCache as Ur, producerRegionsFromState as Ut, cyan as V, buildAssetRedirectMap as Vn, AssemblyReader as Vr, requireConfigString as Vt, red as W, rewriteTemplateAssetReferences as Wn, resolveBucketRegion as Wr, withSharedDrainBudget as Wt, secretBearing as X, isCrossRegionRedirect as Xn, CdkdError as Xr, applyRoleArnIfSet as Xt, isExportAliasCollision as Y, getBootstrapMarkerKey as Yn, AssetError as Yr, s3BucketWebsiteUrl as Yt, secretBearingStateKeyWarning as Z, parseBootstrapMarker as Zn, ConfigError as Zr, DiffCalculator as Zt, isSecretBearingReferenceString as _, normalizeAwsError as _i, maskSecretsInText as _n, getDockerImageBySourceHash as _r, deleteSkipReason as _t, DeploymentEventsStore as a, LockError as ai, STATE_SOURCED_BASELINE_RULES as an, describeDockerExecFailure as ar, createMaskedRetryLogger as at, PRE_DELETE_SNAPSHOT_TYPES as b, isRetryableTransientError as bi, redactSecretsForState as bn, getDefaultStateBucketName as br, IntrinsicFunctionResolver as bt, replayFailedOperations as c, ProvisioningError as ci, TEMPLATE_SOURCED_RULES as cn, formatDockerLoginError as cr, beginCommandInterruptScope as ct, updatePartialReason as d, StackHasActiveImportsError as di, dynamicReferenceTokens as dn, redactDockerArgvValues as dr, isInterruptedWaitError as dt, DeployCancelledError as ei, findSilentDropProperties as en, validateContainerRepoName as er, IAMRoleProvider as et, withResourceDeadline as f, StackTerminationProtectionError as fi, errorCauseChain as fn, runDockerForeground as fr, startInterruptWatch as ft, bagHoldsSecretExpression as g, isCdkdError as gi, maskSecretsInError as gn, AssetManifestLoader as gr, deleteIndeterminateGuards as gt, computeImplicitDeleteEdges as h, formatError as hi, markSameGenerationBag as hn, stripControlChars as hr, UNSPECIFIED_SKIP_REASON as ht, DeploymentEventsReader as i, LocalStartServiceError as ii, TemplateParser as in, describeDockerCapturedOutput as ir, wouldReturnToSdkProvider as it, MULTI_REGION_RECREATE_BLOCKED_TYPES as j, DEFAULT_STATE_PREFIX as jn, CFN_TEMPLATE_BODY_LIMIT as jr, assertRegionMatch as jt, makeSiblingClaimReader as k, shellQuote as kn, stateBucketExistenceConfirmed as kr, normalizeAwsTagsToCfn as kt, replayRollback as l, ResourceTimeoutError as li, carriesSecretMask as ln, getDockerCmd as lr, endCommandInterruptScope as lt, IMPLICIT_DELETE_DEPENDENCIES as m, SynthesisError as mi, isSingleDynamicReferenceToken as mn, escapeRegExp$1 as mr, slowCcOperationTimeoutMs as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, IntrinsicResolutionRefusalError as ni, withRetry as nn, describeAwsFailure as nr, clearOnUpdateRemoval as nt, planFailedOps as o, NestedStackChildDirectDestroyError as oi, STATE_SOURCED_CROSS_GENERATION_RULES as on, describeDockerFailure as or, maskDeep as ot, maskingRetryLogger as p, StateError as pi, identityKeyFor as pn, runDockerStreaming as pr, CloudControlProvider as pt, collectPublishedOutputNames as q, assertAssetBucketRegion as qn, resetAwsClients as qr, s3BucketDualStackDomainName as qt, DeployEngine as r, LocalInvokeBuildError as ri, DagBuilder as rn, buildDockerImage as rr, ProviderRegistry as rt, planRollback as s, PartialFailureError as si, STATE_SOURCED_READBACK_RULES as sn, dockerSpawnEnvWithSensitive as sr, maskerOrIdentity as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, DynamicReferenceRegionAmbiguousError as ti, describeTypeWithThrottleRetry as tn, buildDenyExternalAccessPolicy as tr, collectInlinePolicyNamesManagedBySiblings as tt, updatePartialMessage as u, ResourceUpdateNotSupportedError as ui, createSecretMasker as un, partitionSensitiveEnv as ur, interruptWatchListenerCount as ut, bindingSkippedOutputs as v, withErrorHandling as vi, recordMaskOnlyValue as vn, Synthesizer as vr, disableInstanceApiTermination as vt, isFinalSnapshotError as w, markRedactedCause as wi, rebuildClientForBucketRegion as wn, resolveCaptureObservedState as wr, getAccountInfo as wt, buildFinalSnapshotIdentifier as x, isThrottlingError as xi, scrubResourceRecord as xn, getLegacyStateBucketName as xr, carriesDynamicReference as xt, ATOMIC_FINAL_SNAPSHOT_TYPES as y, isMarkedNonRetryable as yi, recoverMaskedOutput as yn, synthesisStatusMessage as yr, isTerminationProtectionPropagationError as yt, formatResourceLine as z, stringifyValue as zn, canonicalizeRegion as zr, requireConfigArray as zt };
41045
- //# sourceMappingURL=deploy-engine-Nn9j2GPK.js.map
41675
+ export { secretSafeKeyDisplay as $, parseBootstrapMarker as $n, ConfigError as $r, DiffCalculator as $t, planOrphanAdoption as A, formatLockExpiry as An, resolveUseCdkBootstrapAssets as Ar, WAFv2WebACLProvider as At, formatResourceLine as B, AssetPublisher as Bn, PARTITION_TABLE as Br, replayWarn as Bt, createPreDeleteFinalSnapshot as C, isThrottlingError as Ci, scrubResourceRecord as Cn, getLegacyStateBucketName as Cr, cfnRefValueFromPhysicalId as Ct, makeCanonicalizePropertiesFn as D, retryClassificationText as Di, buildForceUnlockCommand as Dn, resolveSkipPrefix as Dr, parameterTypeMayLoseSecretIdentity as Dt, unsupportedFinalSnapshotError as E, markRedactedCause as Ei, rebuildClientForBucketRegion as En, resolveCaptureObservedState as Er, isUnboundTemplateParameter as Et, WARM_THROUGHPUT_MEMBERS as F, importableOutputKeys as Fn, MIGRATE_TMP_PREFIX as Fr, coerceCfnInteger as Ft, red as G, loadPublishableAssetManifest as Gn, clearBucketRegionCache as Gr, producerRegionsFromState as Gt, cyan as H, WorkGraph as Hn, derivePartitionAndUrlSuffix as Hr, requireConfigObject as Ht, coerceWarmThroughput as I, importableOutputs as In, findLargeInlineResources as Ir, configBooleanRefusal as It, collectPublishedOutputNames as J, BOOTSTRAP_MARKER_PREFIX as Jn, getAwsClients as Jr, s3BucketDomainName as Jt, yellow as K, rewriteTemplateAssetReferences as Kn, resolveBucketRegion as Kr, withSharedDrainBudget as Kt, isWarmThroughputDecrease as L, orphansAfterRollback as Ln, uploadCfnTemplate as Lr, configIntegerRefusal as Lt, isStatefulRecreateTargetForReplace as M, CUSTOM_RESOURCE_RESPONSE_PREFIX as Mn, warnDeprecatedNoPrefixCliFlag as Mr, resolveExplicitPhysicalId as Mt, isStatefulRecreateTargetSync as N, DEFAULT_STATE_PREFIX as Nn, CFN_TEMPLATE_BODY_LIMIT as Nr, assertRegionMatch as Nt, extractDeploymentEventError as O, __exportAll as Oi, buildLockContentionMessage as On, resolveStateBucketWithDefault as Or, refStateLookupFromResource as Ot, renderStatefulReason as P, exportNamesCarriedFrom as Pn, CFN_TEMPLATE_URL_LIMIT as Pr, coerceCfnBoolean as Pt, secretBearingStateKeyWarning as Q, isCrossRegionRedirect as Qn, CdkdError as Qr, applyRoleArnIfSet as Qt, toCfnInteger as R, orphansCarriedFrom as Rn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Rr, configStringRefusal as Rt, ccRoutedFinalSnapshotError as S, isRetryableTransientError as Si, redactSecretsForState as Sn, getDefaultStateBucketName as Sr, carriesDynamicReference as St, refusesFinalSnapshot as T, markNonRetryable as Ti, S3StateBackend as Tn, resolveAutoAssetStorage as Tr, getAccountInfo as Tt, gray as U, buildAssetRedirectMap as Un, AssemblyReader as Ur, requireConfigString as Ut, bold as V, stringifyValue as Vn, canonicalizeRegion as Vr, requireConfigArray as Vt, green as W, createAssetRedirectResolver as Wn, processStackMessages as Wr, classifyReplaySecretRegion as Wt, isExportAliasCollision as X, ensureAssetStorage as Xn, setAwsClients as Xr, s3BucketRegionalDomainName as Xt, exportAliasCollisionScrubWarning as Y, assertAssetBucketRegion as Yn, resetAwsClients as Yr, s3BucketDualStackDomainName as Yt, secretBearing as Z, getBootstrapMarkerKey as Zn, AssetError as Zr, s3BucketWebsiteUrl as Zt, isSecretBearingReferenceString as _, formatError as _i, markSameGenerationBag as _n, stripControlChars as _r, deleteIndeterminateGuards as _t, DeploymentEventsStore as a, LocalInvokeBuildError as ai, DagBuilder as an, buildDockerImage as ar, wouldReturnToSdkProvider as at, PRE_DELETE_SNAPSHOT_TYPES as b, withErrorHandling as bi, recordMaskOnlyValue as bn, Synthesizer as br, isTerminationProtectionPropagationError as bt, replayFailedOperations as c, NestedStackChildDirectDestroyError as ci, STATE_SOURCED_CROSS_GENERATION_RULES as cn, describeDockerFailure as cr, maskerOrIdentity as ct, updatePartialReason as d, ResourceTimeoutError as di, carriesSecretMask as dn, getDockerCmd as dr, interruptWatchListenerCount as dt, CrossAccountSecretRefusalError as ei, INTRINSIC_KEYS as en, readBootstrapMarkerBody as er, getCurrentResourceSecrets as et, withResourceDeadline as f, ResourceUpdateNotSupportedError as fi, createSecretMasker as fn, partitionSensitiveEnv as fr, isInterruptedWaitError as ft, bagHoldsSecretExpression as g, SynthesisError as gi, isSingleDynamicReferenceToken as gn, escapeRegExp$1 as gr, UNSPECIFIED_SKIP_REASON as gt, computeImplicitDeleteEdges as h, StateError as hi, identityKeyFor as hn, runDockerStreaming as hr, slowCcOperationTimeoutMs as ht, DeploymentEventsReader as i, IntrinsicResolutionRefusalError as ii, withRetry as in, describeAwsFailure as ir, ProviderRegistry as it, MULTI_REGION_RECREATE_BLOCKED_TYPES as j, shellQuote as jn, stateBucketExistenceConfirmed as jr, normalizeAwsTagsToCfn as jt, makeSiblingClaimReader as k, forceQuitRecoveryClause as kn, resolveStateBucketWithDefaultAndSource as kr, isSettledInstanceState as kt, replayRollback as l, PartialFailureError as li, STATE_SOURCED_READBACK_RULES as ln, dockerSpawnEnvWithSensitive as lr, beginCommandInterruptScope as lt, IMPLICIT_DELETE_DEPENDENCIES as m, StackTerminationProtectionError as mi, errorCauseChain as mn, runDockerForeground as mr, CloudControlProvider as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, DeployCancelledError as ni, findSilentDropProperties as nn, validateContainerRepoName as nr, collectInlinePolicyNamesManagedBySiblings as nt, planFailedOps as o, LocalStartServiceError as oi, TemplateParser as on, describeDockerCapturedOutput as or, createMaskedRetryLogger as ot, maskingRetryLogger as p, StackHasActiveImportsError as pi, dynamicReferenceTokens as pn, redactDockerArgvValues as pr, startInterruptWatch as pt, collectDeclaredOutputNames as q, AssetModeResolver as qn, AwsClients as qr, s3BucketArn as qt, DeployEngine as r, DynamicReferenceRegionAmbiguousError as ri, describeTypeWithThrottleRetry as rn, buildDenyExternalAccessPolicy as rr, clearOnUpdateRemoval as rt, planRollback as s, LockError as si, STATE_SOURCED_BASELINE_RULES as sn, describeDockerExecFailure as sr, maskDeep as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, DependencyError as ti, findActionableSilentDrops as tn, validateAssetBucketName as tr, IAMRoleProvider as tt, updatePartialMessage as u, ProvisioningError as ui, TEMPLATE_SOURCED_RULES as un, formatDockerLoginError as ur, endCommandInterruptScope as ut, bindingSkippedOutputs as v, isCdkdError as vi, maskSecretsInError as vn, AssetManifestLoader as vr, deleteSkipReason as vt, isFinalSnapshotError as w, isTransientServerError as wi, LockManager as wn, resolveApp as wr, coerceParameterTypedValue as wt, buildFinalSnapshotIdentifier as x, isMarkedNonRetryable as xi, recoverMaskedOutput as xn, synthesisStatusMessage as xr, IntrinsicFunctionResolver as xt, ATOMIC_FINAL_SNAPSHOT_TYPES as y, normalizeAwsError as yi, maskSecretsInText as yn, getDockerImageBySourceHash as yr, disableInstanceApiTermination as yt, toFiniteNumber as z, shouldRetainResource as zn, expectedOwnerParam as zr, readConfigString as zt };
41676
+ //# sourceMappingURL=deploy-engine-DD7JE_Ic.js.map