@go-to-k/cdkd 0.257.3 → 0.258.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { a as getLiveRenderer, d as generateResourceNameWithFallback, l as apply
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
4
4
  import { CloudControlClient, CreateResourceCommand, DeleteResourceCommand, GetResourceCommand, GetResourceRequestStatusCommand, ListResourcesCommand, UpdateResourceCommand } from "@aws-sdk/client-cloudcontrol";
5
- import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand, ListRolesCommand, NoSuchEntityException, PutRolePermissionsBoundaryCommand, PutRolePolicyCommand, RemoveRoleFromInstanceProfileCommand, TagRoleCommand, UntagRoleCommand, UpdateAssumeRolePolicyCommand, UpdateRoleCommand } from "@aws-sdk/client-iam";
5
+ import { AttachRolePolicyCommand, CreateRoleCommand, DeleteRoleCommand, DeleteRolePermissionsBoundaryCommand, DeleteRolePolicyCommand, DetachRolePolicyCommand, GetRoleCommand, GetRolePolicyCommand, IAMClient, ListAttachedRolePoliciesCommand, ListInstanceProfilesForRoleCommand, ListRolePoliciesCommand, ListRoleTagsCommand, NoSuchEntityException, PutRolePermissionsBoundaryCommand, PutRolePolicyCommand, RemoveRoleFromInstanceProfileCommand, TagRoleCommand, UntagRoleCommand, UpdateAssumeRolePolicyCommand, UpdateRoleCommand } from "@aws-sdk/client-iam";
6
6
  import { SQSClient } from "@aws-sdk/client-sqs";
7
7
  import { PublishCommand, SNSClient } from "@aws-sdk/client-sns";
8
8
  import { GetFunctionCommand, GetFunctionUrlConfigCommand, InvokeCommand, LambdaClient, UpdateFunctionConfigurationCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
@@ -19,6 +19,7 @@ import { CloudWatchClient } from "@aws-sdk/client-cloudwatch";
19
19
  import { CloudWatchLogsClient } from "@aws-sdk/client-cloudwatch-logs";
20
20
  import { BedrockAgentCoreControlClient } from "@aws-sdk/client-bedrock-agentcore-control";
21
21
  import { ACMClient } from "@aws-sdk/client-acm";
22
+ import { LambdaMicrovmsClient } from "@aws-sdk/client-lambda-microvms";
22
23
  import { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
23
24
  import { basename, isAbsolute, join, resolve } from "node:path";
24
25
  import { spawn } from "node:child_process";
@@ -35,7 +36,7 @@ import { DescribeDBClustersCommand, DescribeDBInstancesCommand, RDSClient } from
35
36
  import { DescribeReplicationGroupsCommand, ElastiCacheClient } from "@aws-sdk/client-elasticache";
36
37
  import { DescribeClustersCommand, RedshiftClient } from "@aws-sdk/client-redshift";
37
38
  import { DescribeDomainCommand, OpenSearchClient } from "@aws-sdk/client-opensearch";
38
- import { CreateWebACLCommand, DeleteWebACLCommand, GetWebACLCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$11, ListWebACLsCommand, TagResourceCommand as TagResourceCommand$12, UntagResourceCommand as UntagResourceCommand$12, UpdateWebACLCommand, WAFNonexistentItemException, WAFV2Client } from "@aws-sdk/client-wafv2";
39
+ import { CreateWebACLCommand, DeleteWebACLCommand, GetWebACLCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$11, TagResourceCommand as TagResourceCommand$13, UntagResourceCommand as UntagResourceCommand$13, UpdateWebACLCommand, WAFNonexistentItemException, WAFV2Client } from "@aws-sdk/client-wafv2";
39
40
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
40
41
 
41
42
  //#region \0rolldown/runtime.js
@@ -609,6 +610,7 @@ var AwsClients = class {
609
610
  cloudWatchLogsClient;
610
611
  bedrockAgentCoreControlClient;
611
612
  acmClient;
613
+ lambdaMicrovmsClient;
612
614
  config;
613
615
  constructor(config = {}) {
614
616
  this.config = config;
@@ -893,6 +895,28 @@ var AwsClients = class {
893
895
  return this.getACMClient();
894
896
  }
895
897
  /**
898
+ * Get Lambda MicroVMs client.
899
+ *
900
+ * Backs `AWS::Lambda::MicrovmImage` (`LambdaMicrovmImageProvider`). This is
901
+ * the dedicated `lambda-microvms` service, NOT `@aws-sdk/client-lambda` —
902
+ * the MicroVM image / MicroVM run APIs live in their own service model.
903
+ * Region-scoped: MicroVM images and their code-artifact S3 buckets must be
904
+ * in the same region.
905
+ */
906
+ getLambdaMicrovmsClient() {
907
+ if (!this.lambdaMicrovmsClient) this.lambdaMicrovmsClient = new LambdaMicrovmsClient({
908
+ ...this.config.region && { region: this.config.region },
909
+ ...this.config.credentials && { credentials: this.config.credentials }
910
+ });
911
+ return this.lambdaMicrovmsClient;
912
+ }
913
+ /**
914
+ * Convenience getter for Lambda MicroVMs client
915
+ */
916
+ get lambdaMicrovms() {
917
+ return this.getLambdaMicrovmsClient();
918
+ }
919
+ /**
896
920
  * Get CloudWatch client
897
921
  */
898
922
  getCloudWatchClient() {
@@ -963,6 +987,7 @@ var AwsClients = class {
963
987
  this.cloudWatchLogsClient?.destroy();
964
988
  this.bedrockAgentCoreControlClient?.destroy();
965
989
  this.acmClient?.destroy();
990
+ this.lambdaMicrovmsClient?.destroy();
966
991
  }
967
992
  };
968
993
  /**
@@ -2488,6 +2513,25 @@ function findLargeInlineResources(template, threshold = LARGE_INLINE_RESOURCE_TH
2488
2513
  const WAITER_MAX_WAIT_SECONDS = 600;
2489
2514
  const PARAMETER_PLACEHOLDER = "cdkd-macro-expand-placeholder";
2490
2515
  /**
2516
+ * AWS's managed pre-deployment validation hooks
2517
+ * (`AWS::EarlyValidation::*`, e.g. `ResourceExistenceCheck`) can
2518
+ * reject the transient expansion changeset INTERMITTENTLY (issue
2519
+ * #1151: two consecutive rejections followed by a clean pass on the
2520
+ * same template minutes later, with the same named resources
2521
+ * existing throughout). The changeset is never executed — cdkd only
2522
+ * reads the Processed-stage template — so a validation-hook rejection
2523
+ * carries no real risk and is worth retrying with a fresh transient
2524
+ * stack before failing the whole run.
2525
+ */
2526
+ const EARLY_VALIDATION_MAX_ATTEMPTS = 3;
2527
+ const EARLY_VALIDATION_RETRY_BASE_DELAY_MS = 2e3;
2528
+ /** Matches the changeset FAILED StatusReason emitted by the hook family. */
2529
+ function isEarlyValidationRejection(err) {
2530
+ return err instanceof MacroExpansionError && /AWS::EarlyValidation::/.test(err.message);
2531
+ }
2532
+ /** Test seam: overridable sleep so retry tests don't wait wall-clock. */
2533
+ const retryDelays = { sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)) };
2534
+ /**
2491
2535
  * Capabilities sent on every `CreateChangeSet` call:
2492
2536
  *
2493
2537
  * - `CAPABILITY_AUTO_EXPAND` is the load-bearing one — required for CFn
@@ -2583,6 +2627,19 @@ const PARAMETER_TYPE_PLACEHOLDERS = {
2583
2627
  async function expandMacros(template, opts) {
2584
2628
  const logger = getLogger().child("MacroExpander");
2585
2629
  if (!containsMacro(template)) return template;
2630
+ for (let attempt = 1;; attempt++) try {
2631
+ return await expandMacrosAttempt(template, opts, logger);
2632
+ } catch (err) {
2633
+ if (attempt < EARLY_VALIDATION_MAX_ATTEMPTS && isEarlyValidationRejection(err)) {
2634
+ const delayMs = EARLY_VALIDATION_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
2635
+ logger.warn(`Macro expansion changeset was rejected by an AWS EarlyValidation hook (attempt ${attempt}/${EARLY_VALIDATION_MAX_ATTEMPTS}); this rejection is known to be intermittent — retrying with a fresh transient stack in ${delayMs / 1e3}s...`);
2636
+ await retryDelays.sleep(delayMs);
2637
+ continue;
2638
+ }
2639
+ throw err;
2640
+ }
2641
+ }
2642
+ async function expandMacrosAttempt(template, opts, logger) {
2586
2643
  const macros = enumerateMacros(template);
2587
2644
  logger.debug(`Macro expansion: detected transforms [${macros.join(", ")}], starting CFn round-trip...`);
2588
2645
  const transientStackName = `cdkd-macro-expand-${randomUUID().slice(0, 16)}`;
@@ -3131,19 +3188,7 @@ var Synthesizer = class {
3131
3188
  this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);
3132
3189
  const manifest = this.assemblyReader.readManifest(appPath);
3133
3190
  const stacks = this.assemblyReader.getAllStacks(appPath, manifest);
3134
- const presynthRegion = options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"];
3135
- let presynthAccountId;
3136
- try {
3137
- const stsClient = new STSClient({ ...presynthRegion && { region: presynthRegion } });
3138
- presynthAccountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
3139
- stsClient.destroy();
3140
- } catch {
3141
- this.logger.debug("Could not resolve AWS account ID via STS (pre-synth branch)");
3142
- }
3143
- await this.expandMacrosForStacks(stacks, options, {
3144
- region: presynthRegion,
3145
- ...presynthAccountId && { accountId: presynthAccountId }
3146
- });
3191
+ if (!options.deferMacroExpansion) await this.expandMacrosForStacks(stacks, options);
3147
3192
  this.logger.debug(`Loaded ${stacks.length} stack(s) from pre-synthesized assembly`);
3148
3193
  return {
3149
3194
  manifest,
@@ -3162,7 +3207,8 @@ var Synthesizer = class {
3162
3207
  "aws:cdk:version-reporting": true,
3163
3208
  "aws:cdk:bundling-stacks": ["**"]
3164
3209
  };
3165
- const region = options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"];
3210
+ const explicitRegion = options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"];
3211
+ const region = explicitRegion || await resolveSdkDefaultRegion(options.profile);
3166
3212
  let accountId;
3167
3213
  try {
3168
3214
  const stsClient = new STSClient({ ...region && { region } });
@@ -3196,8 +3242,8 @@ var Synthesizer = class {
3196
3242
  const manifest = this.assemblyReader.readManifest(outputDir);
3197
3243
  if (!manifest.missing || manifest.missing.length === 0) {
3198
3244
  const stacks = this.assemblyReader.getAllStacks(outputDir, manifest);
3199
- await this.expandMacrosForStacks(stacks, options, {
3200
- region,
3245
+ if (!options.deferMacroExpansion) await this.expandMacrosForStacks(stacks, options, {
3246
+ region: explicitRegion,
3201
3247
  ...accountId && { accountId }
3202
3248
  });
3203
3249
  this.logger.debug(`Synthesis complete: ${stacks.length} stack(s)`);
@@ -3218,10 +3264,16 @@ var Synthesizer = class {
3218
3264
  }
3219
3265
  }
3220
3266
  /**
3221
- * List stack names in CDK app
3267
+ * List stack names in CDK app. Names come straight from the
3268
+ * manifest, so the macro-expansion pre-pass is skipped entirely —
3269
+ * listing a macro app needs no AWS region or CloudFormation access
3270
+ * (issue #1150).
3222
3271
  */
3223
3272
  async listStacks(options) {
3224
- return (await this.synthesize(options)).stacks.map((s) => s.stackName);
3273
+ return (await this.synthesize({
3274
+ ...options,
3275
+ deferMacroExpansion: true
3276
+ })).stacks.map((s) => s.stackName);
3225
3277
  }
3226
3278
  /**
3227
3279
  * Per-stack macro-expansion pass (Issue #463). Mutates each stack's
@@ -3230,23 +3282,36 @@ var Synthesizer = class {
3230
3282
  * analyzer / provisioner pipeline consumes the templates, so every
3231
3283
  * downstream stage sees the post-expansion shape.
3232
3284
  *
3233
- * Skipped silently when no stack carries a macro — pure no-op cost.
3234
- * When a macro IS detected and the caller did NOT thread a
3235
- * region into the synthesizer, falls back to resolving region from
3236
- * the synthesized stack's environment (set by `cdk.Stack.region`).
3237
- * Throws `SynthesisError` when no region can be resolved (the
3238
- * upstream caller treats it as a synth failure) and propagates
3239
- * `MacroExpansionError` (from {@link expandMacros}) on any CFn-side
3240
- * failure during the round-trip.
3285
+ * PUBLIC since issue #1150: selection-aware commands (deploy / diff)
3286
+ * synthesize with `deferMacroExpansion: true` and invoke this
3287
+ * directly with ONLY the stacks they will actually consume, so an
3288
+ * unselected macro-carrying sibling can never fail or slow the run.
3289
+ *
3290
+ * Skipped silently when no stack carries a macro pure no-op cost
3291
+ * (in particular, no STS hop). When a macro IS detected and the
3292
+ * caller did NOT thread a resolved region/account, both are
3293
+ * resolved here (STS for the default state bucket; the region chain
3294
+ * below for the CFn client). Throws `SynthesisError` when no region
3295
+ * can be resolved (the upstream caller treats it as a synth
3296
+ * failure) and propagates `MacroExpansionError` (from {@link
3297
+ * expandMacros}) on any CFn-side failure during the round-trip.
3241
3298
  */
3242
3299
  async expandMacrosForStacks(stacks, options, resolved) {
3243
3300
  const stacksWithMacros = stacks.filter((s) => containsMacro(s.template));
3244
3301
  if (stacksWithMacros.length === 0) return;
3245
- const region = resolved?.region || options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"] || stacksWithMacros[0]?.region;
3302
+ const region = resolved?.region || options.region || process.env["AWS_REGION"] || process.env["AWS_DEFAULT_REGION"] || stacksWithMacros[0]?.region || await resolveSdkDefaultRegion(options.profile);
3246
3303
  if (!region) throw new SynthesisError(`Stack(s) [${stacksWithMacros.map((s) => s.stackName).join(", ")}] use CloudFormation macros (Transform / Fn::Transform) but cdkd could not resolve an AWS region for the expansion round-trip. Set AWS_REGION, pass --region <r>, or set env: { region: '<r>' } in your CDK Stack constructor.`);
3304
+ let accountId = resolved?.accountId;
3305
+ if (resolved === void 0 && !options.stateBucket) try {
3306
+ const stsClient = new STSClient({ region });
3307
+ accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
3308
+ stsClient.destroy();
3309
+ } catch {
3310
+ this.logger.debug("Could not resolve AWS account ID via STS (macro expansion)");
3311
+ }
3247
3312
  let stateBucket;
3248
3313
  if (options.stateBucket) stateBucket = options.stateBucket;
3249
- else if (resolved?.accountId) stateBucket = `cdkd-state-${resolved.accountId}`;
3314
+ else if (accountId) stateBucket = `cdkd-state-${accountId}`;
3250
3315
  else {
3251
3316
  const oversize = stacksWithMacros.find((s) => JSON.stringify(s.template).length > 51200);
3252
3317
  if (oversize) throw new SynthesisError(`Stack '${oversize.stackName}' uses CloudFormation macros AND its serialized template exceeds the 51,200-byte inline TemplateBody limit, so cdkd must upload the template to S3 for the transient expansion changeset. cdkd could not resolve a state bucket: STS GetCallerIdentity failed AND --state-bucket was not provided. Pass --state-bucket <name> (cdkd uses the same bucket as cdkd deploy state storage; typically 'cdkd-state-<accountId>').`);
@@ -3268,6 +3333,31 @@ var Synthesizer = class {
3268
3333
  }
3269
3334
  };
3270
3335
  /**
3336
+ * Resolve the AWS SDK's own default region (env vars, shared config
3337
+ * file profile region, etc.) — the same chain every cdkd AWS client
3338
+ * consults implicitly when constructed without an explicit region.
3339
+ * Returns `undefined` when the chain yields nothing so callers can
3340
+ * fall through to their own hard-error (issue #1149: the
3341
+ * macro-expansion pre-pass previously checked only explicit
3342
+ * option/env sources and hard-errored for users whose region lives in
3343
+ * `~/.aws/config`).
3344
+ *
3345
+ * Implemented by asking a throwaway STS client for its resolved
3346
+ * region provider — that is exactly the chain the real provisioning
3347
+ * clients use, so the two can never diverge.
3348
+ */
3349
+ async function resolveSdkDefaultRegion(profile) {
3350
+ let client;
3351
+ try {
3352
+ client = new STSClient({ ...profile && { profile } });
3353
+ return await client.config.region() || void 0;
3354
+ } catch {
3355
+ return;
3356
+ } finally {
3357
+ client?.destroy?.();
3358
+ }
3359
+ }
3360
+ /**
3271
3361
  * Check if two sets contain the same elements
3272
3362
  */
3273
3363
  function setsEqual(a, b) {
@@ -8158,21 +8248,6 @@ function resolveExplicitPhysicalId(input, nameProperty) {
8158
8248
  }
8159
8249
  }
8160
8250
  /**
8161
- * The standard tag CDK puts on every deployed resource — its construct path
8162
- * within the app, e.g. `MyStack/MyConstruct/MyBucket`. Used as the lookup key
8163
- * when no explicit name is in the template.
8164
- */
8165
- const CDK_PATH_TAG = "aws:cdk:path";
8166
- /**
8167
- * Match an AWS resource's tag set against the CDK path the template carries.
8168
- * Returns true if the resource was deployed by the same CDK construct.
8169
- */
8170
- function matchesCdkPath(tags, cdkPath) {
8171
- if (!tags || !cdkPath) return false;
8172
- for (const t of tags) if (t.Key === "aws:cdk:path" && t.Value === cdkPath) return true;
8173
- return false;
8174
- }
8175
- /**
8176
8251
  * Re-shape an AWS tag list (any of the common shapes — array of `{Key, Value}`,
8177
8252
  * map keyed by tag name, or v2-style array of `{TagKey, TagValue}`) into the
8178
8253
  * canonical CFn shape (`Array<{Key, Value}>`) that cdkd state holds, with
@@ -8215,330 +8290,6 @@ function normalizeAwsTagsToCfn(tags) {
8215
8290
  return out;
8216
8291
  }
8217
8292
 
8218
- //#endregion
8219
- //#region src/deployment/retryable-errors.ts
8220
- /**
8221
- * Patterns that mark an AWS error as a transient/retryable failure.
8222
- * Each entry is a substring match against the error message; all of these
8223
- * are situations where the same call typically succeeds after a short delay
8224
- * because of eventual consistency or just-created-dependency propagation.
8225
- */
8226
- const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
8227
- "cannot be assumed",
8228
- "Firehose is unable to assume role",
8229
- "is unable to assume provided role",
8230
- "role defined for the function",
8231
- "not authorized to perform",
8232
- "execution role",
8233
- "trust policy",
8234
- "Role validation failed",
8235
- "does not have required permissions",
8236
- "Trusted Entity",
8237
- "currently in the following state: Pending",
8238
- "has dependencies and cannot be deleted",
8239
- "can't be deleted since it has",
8240
- "DependencyViolation",
8241
- "does not exist",
8242
- "Schema is currently being altered",
8243
- "Invalid principal in policy",
8244
- "Policy Error: PrincipalNotFound",
8245
- "Invalid value for the parameter Policy",
8246
- "required permissions for: ENHANCED_MONITORING",
8247
- "Caught ServiceAccessDeniedException",
8248
- "permissions required to assume the role",
8249
- "authorized to assume the provided role",
8250
- "conflicting conditional operation",
8251
- "scheduled for deletion",
8252
- "Cannot access stream",
8253
- "Please ensure the role can perform",
8254
- "KMS key is invalid for CreateGrant",
8255
- "Policy contains a statement with one or more invalid principals",
8256
- "Invalid IAM Instance Profile",
8257
- "Invalid InstanceProfile",
8258
- "Failed to authorize instance profile",
8259
- "Could not deliver test message",
8260
- "wait 60 seconds",
8261
- "concurrent update operation",
8262
- "because it is in use",
8263
- "Rate exceeded"
8264
- ];
8265
- /**
8266
- * HTTP status codes that always indicate a transient failure worth retrying.
8267
- * 429 = Too Many Requests (throttle), 503 = Service Unavailable.
8268
- */
8269
- const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
8270
- /**
8271
- * AWS SDK v3 canonical throttling error names. Mirrors
8272
- * `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
8273
- * error (or wrapped cause) whose `name` is one of these is a transient rate-
8274
- * limit rejection worth retrying with backoff. Detecting by NAME is more
8275
- * robust than by HTTP status because most AWS throttles surface as HTTP 400
8276
- * (not 429) with the throttling signal carried only in the error code / name
8277
- * (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
8278
- */
8279
- const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
8280
- "BandwidthLimitExceeded",
8281
- "EC2ThrottledException",
8282
- "LimitExceededException",
8283
- "PriorRequestNotComplete",
8284
- "ProvisionedThroughputExceededException",
8285
- "RequestLimitExceeded",
8286
- "RequestThrottled",
8287
- "RequestThrottledException",
8288
- "SlowDown",
8289
- "ThrottledException",
8290
- "Throttling",
8291
- "ThrottlingException",
8292
- "TooManyRequestsException",
8293
- "TransactionInProgressException"
8294
- ]);
8295
- /**
8296
- * Walk the error + its `.cause` chain (bounded) looking for a rate-limit
8297
- * signal — either an AWS SDK v3 throttling error `name`
8298
- * ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
8299
- * ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
8300
- *
8301
- * cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
8302
- * typically one cause-link deep; the bounded walk also tolerates SDK errors
8303
- * that nest a `$response`/cause without exploding on a cyclic chain.
8304
- *
8305
- * BOTH signals are checked at EVERY depth. An earlier version checked the name
8306
- * to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
8307
- * links deep was missed. This is the single shared cause-walk: the read-only
8308
- * import tag walk (`src/provisioning/import-tag-walk.ts`) composes its own
8309
- * classifier on top of this function rather than re-implementing the traversal,
8310
- * so the two cannot drift apart again.
8311
- */
8312
- function isThrottlingError(error) {
8313
- let current = error;
8314
- for (let depth = 0; depth < 5 && current != null; depth++) {
8315
- const name = current.name;
8316
- if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
8317
- const status = current.$metadata?.httpStatusCode;
8318
- if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
8319
- current = current.cause;
8320
- }
8321
- return false;
8322
- }
8323
- /**
8324
- * Determine whether an AWS error should be retried.
8325
- *
8326
- * Checks (in order):
8327
- * 1. Rate-limit signal on the error or any wrapped cause — throttling error
8328
- * `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
8329
- * 429, so the name check carries most of the weight). See
8330
- * {@link isThrottlingError}.
8331
- * 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
8332
- */
8333
- function isRetryableTransientError(error, message) {
8334
- if (isThrottlingError(error)) return true;
8335
- return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
8336
- }
8337
-
8338
- //#endregion
8339
- //#region src/deployment/retry.ts
8340
- /**
8341
- * Retry helper for resource provisioning operations that hit transient
8342
- * AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
8343
- * dependency violations, etc.).
8344
- *
8345
- * Extracted from DeployEngine so the backoff schedule can be unit-tested
8346
- * in isolation. The retryable-error classifier itself lives in
8347
- * `./retryable-errors.ts`.
8348
- */
8349
- const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
8350
- /**
8351
- * Run `operation`, retrying transient failures with exponential backoff
8352
- * capped at `maxDelayMs`.
8353
- *
8354
- * Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
8355
- * 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
8356
- *
8357
- * Non-retryable errors are rethrown immediately. The transient-error
8358
- * classifier is `isRetryableTransientError` from ./retryable-errors.ts.
8359
- */
8360
- async function withRetry(operation, logicalId, opts = {}) {
8361
- const maxRetries = opts.maxRetries ?? 8;
8362
- const initialDelayMs = opts.initialDelayMs ?? 1e3;
8363
- const maxDelayMs = opts.maxDelayMs ?? 8e3;
8364
- const sleep = opts.sleep ?? defaultSleep;
8365
- let lastError;
8366
- for (let attempt = 0; attempt <= maxRetries; attempt++) try {
8367
- return await operation();
8368
- } catch (error) {
8369
- lastError = error;
8370
- const message = error instanceof Error ? error.message : String(error);
8371
- if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
8372
- const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
8373
- opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
8374
- for (let waited = 0; waited < delay; waited += 1e3) {
8375
- if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
8376
- await sleep(Math.min(1e3, delay - waited));
8377
- }
8378
- }
8379
- throw lastError;
8380
- }
8381
-
8382
- //#endregion
8383
- //#region src/provisioning/import-tag-walk.ts
8384
- /**
8385
- * Shared, throttle-tolerant `aws:cdk:path` tag walk for `ResourceProvider.import`.
8386
- *
8387
- * Providers that adopt a resource without an explicit name property fall back
8388
- * to step 3 of the lookup order documented in `./import-helpers.ts`: enumerate
8389
- * the service's `List*`/`Describe*` pages, then issue ONE per-candidate read
8390
- * (`DescribeX` / `ListTagsForResource`) to obtain the tag set — the list
8391
- * summaries usually do not carry tags. That is an inherent **N+1** read
8392
- * pattern: an account with many resources of the type produces one API call
8393
- * per candidate in a tight loop, which is exactly the shape AWS rate-limits.
8394
- *
8395
- * Every provider previously hand-rolled this loop with NO backoff, so a single
8396
- * throttled `Describe*` aborted the whole `cdkd import` run. This helper
8397
- * centralises the loop and wraps BOTH the list page fetch and the per-candidate
8398
- * describe in the deploy engine's `withRetry` with exponential backoff.
8399
- *
8400
- * ## Why a narrower classifier than `isRetryableTransientError`
8401
- *
8402
- * The deploy engine's classifier is tuned for the WRITE path: it also treats
8403
- * eventual-consistency phrasings like `does not exist` / `not authorized to
8404
- * perform` as transient, because a just-created dependency legitimately needs a
8405
- * moment to propagate. On a read-only import walk those messages mean the
8406
- * opposite — the candidate really is gone, or the caller's credentials really
8407
- * lack the permission — and retrying them burns the full backoff budget per
8408
- * candidate before surfacing the true error.
8409
- *
8410
- * So the walk retries throttling ONLY, reusing the deploy engine's throttle
8411
- * tables verbatim ({@link THROTTLING_ERROR_NAMES} /
8412
- * {@link RETRYABLE_HTTP_STATUS_CODES}) rather than maintaining a second copy.
8413
- *
8414
- * ## Batching
8415
- *
8416
- * Batched tag reads are deliberately NOT modelled here: the services this
8417
- * helper currently serves (EMR `DescribeCluster`, DocDB
8418
- * `ListTagsForResource`) expose only single-resource reads. A service that
8419
- * genuinely offers a batch API (e.g. CodeCommit `BatchGetRepositories`) can
8420
- * satisfy several candidates from one call inside its own `describe` callback,
8421
- * or bypass the helper entirely.
8422
- */
8423
- /** Max number of retries after the first attempt, per API call in the walk. */
8424
- const DEFAULT_MAX_RETRIES = 5;
8425
- /** Initial backoff; each retry doubles it up to {@link DEFAULT_MAX_DELAY_MS}. */
8426
- const DEFAULT_INITIAL_DELAY_MS = 500;
8427
- /** Cap for the per-retry delay (0.5s -> 1s -> 2s -> 4s -> 5s, ~12.5s total). */
8428
- const DEFAULT_MAX_DELAY_MS = 5e3;
8429
- /**
8430
- * Wall-clock ceiling for the WHOLE walk (all pages + all candidates), not just
8431
- * one call. Backoff is per-call, so without this a sustained throttle against a
8432
- * large account degrades into `(pages + candidates) x ~12.5s` of near-silent
8433
- * retrying — ~42 minutes for 200 candidates, with no way to tell a slow walk
8434
- * from a hung one. 10 minutes is generous for a healthy walk (a 200-candidate
8435
- * DocDB account completes in seconds when AWS is not throttling) and bounds the
8436
- * pathological case to something a user will wait through.
8437
- */
8438
- const DEFAULT_MAX_WALK_MS = 10 * 6e4;
8439
- /**
8440
- * Ceiling on pages fetched. A service that returns a non-advancing pagination
8441
- * token (a bug, or a marker cdkd echoes back wrongly) would otherwise spin
8442
- * forever. This is now the shared path every migrated provider runs on, so the
8443
- * guard belongs here rather than in each caller.
8444
- */
8445
- const DEFAULT_MAX_PAGES = 1e3;
8446
- /**
8447
- * Whether an error hit during the read-only tag walk is a rate-limit rejection
8448
- * worth backing off on.
8449
- *
8450
- * Delegates the error + `.cause` chain traversal to the deploy engine's
8451
- * {@link isThrottlingError} (throttling error names + retryable HTTP statuses,
8452
- * at every cause depth) and adds the canonical `Rate exceeded` message, which
8453
- * several services return with an HTTP 400 and a service-specific error name.
8454
- *
8455
- * This is deliberately NARROWER than `isRetryableTransientError`: that
8456
- * classifier also treats write-path eventual-consistency phrasings (`does not
8457
- * exist`, `not authorized to perform`) as transient, because a just-created
8458
- * dependency legitimately needs a moment to propagate. On a read-only walk
8459
- * those mean the candidate really is gone or the credentials really lack the
8460
- * permission, and retrying them burns the full backoff budget per candidate
8461
- * before surfacing the true error.
8462
- *
8463
- * Exported for direct unit testing and for providers whose tag walk cannot use
8464
- * {@link importTagWalk} verbatim.
8465
- */
8466
- function isThrottlingLikeError(error, message) {
8467
- return isThrottlingError(error) || message.includes("Rate exceeded");
8468
- }
8469
- /**
8470
- * Module-level test hooks for the walk's backoff.
8471
- *
8472
- * Providers do not expose a retry option on `import()`, so their wiring tests
8473
- * cannot inject `retry.sleep` per call — without this hook every throttle-path
8474
- * wiring test pays a real 0.5s+ backoff wait. Tests set `sleep` to a resolved
8475
- * no-op in `beforeEach` (and clear it in `afterEach`); a per-call
8476
- * `retry.sleep` still wins when supplied. Never set this in production code.
8477
- */
8478
- const importTagWalkTestHooks = {};
8479
- /** Thrown when the walk exceeds its wall-clock budget or page ceiling. */
8480
- var ImportTagWalkLimitError = class extends Error {
8481
- constructor(message) {
8482
- super(message);
8483
- this.name = "ImportTagWalkLimitError";
8484
- }
8485
- };
8486
- /**
8487
- * Paginate `listPage`, `describe` each candidate, and return the first one
8488
- * whose tags carry the requested `aws:cdk:path`. Returns `null` when no
8489
- * candidate matches (or when `cdkPath` is empty).
8490
- *
8491
- * Both callbacks are individually retried with exponential backoff on
8492
- * throttling errors, so one rate-limited call mid-walk no longer aborts the
8493
- * whole import.
8494
- */
8495
- async function importTagWalk(options) {
8496
- const { cdkPath, listPage, describe, tagsOf } = options;
8497
- if (!cdkPath) return null;
8498
- const logicalId = options.logicalId ?? "import";
8499
- const logger = options.retry?.logger ?? getLogger();
8500
- const maxWalkMs = options.retry?.maxWalkMs ?? DEFAULT_MAX_WALK_MS;
8501
- const maxPages = options.retry?.maxPages ?? DEFAULT_MAX_PAGES;
8502
- const sleep = options.retry?.sleep ?? importTagWalkTestHooks.sleep;
8503
- const retryOpts = {
8504
- maxRetries: options.retry?.maxRetries ?? DEFAULT_MAX_RETRIES,
8505
- initialDelayMs: options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS,
8506
- maxDelayMs: options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,
8507
- isRetryable: (message, error) => isThrottlingLikeError(error, message),
8508
- logger,
8509
- ...options.retry?.isInterrupted && { isInterrupted: options.retry.isInterrupted },
8510
- ...options.retry?.onInterrupted && { onInterrupted: options.retry.onInterrupted },
8511
- ...sleep && { sleep }
8512
- };
8513
- const startedAt = Date.now();
8514
- const assertWithinBudget = (stage) => {
8515
- const elapsed = Date.now() - startedAt;
8516
- if (elapsed >= maxWalkMs) throw new ImportTagWalkLimitError(`Timed out looking up ${logicalId} by its aws:cdk:path tag after ${Math.round(elapsed / 1e3)}s (limit ${Math.round(maxWalkMs / 1e3)}s, while ${stage}). AWS is likely throttling the lookup; retry, or pass an explicit physical id with --resource ${logicalId}=<physicalId>.`);
8517
- };
8518
- let marker;
8519
- let pages = 0;
8520
- do {
8521
- assertWithinBudget("listing candidates");
8522
- if (++pages > maxPages) throw new ImportTagWalkLimitError(`Gave up looking up ${logicalId} by its aws:cdk:path tag after ${maxPages} pages (the service may be returning a non-advancing pagination token). Pass an explicit physical id with --resource ${logicalId}=<physicalId>.`);
8523
- const currentMarker = marker;
8524
- const page = await withRetry(() => listPage(currentMarker), logicalId, retryOpts);
8525
- for (const summary of page.items ?? []) {
8526
- assertWithinBudget("reading candidate tags");
8527
- const detail = await withRetry(() => describe(summary), logicalId, retryOpts);
8528
- if (detail === void 0) {
8529
- logger.debug(` ↷ Skipped an ${logicalId} import candidate: its detail lookup returned no result`);
8530
- continue;
8531
- }
8532
- if (matchesCdkPath(tagsOf(detail, summary), cdkPath)) return {
8533
- summary,
8534
- detail
8535
- };
8536
- }
8537
- marker = page.nextMarker;
8538
- } while (marker);
8539
- return null;
8540
- }
8541
-
8542
8293
  //#endregion
8543
8294
  //#region src/provisioning/providers/wafv2-provider.ts
8544
8295
  /**
@@ -8760,14 +8511,14 @@ var WAFv2WebACLProvider = class {
8760
8511
  const tagsToRemove = [];
8761
8512
  for (const k of oldMap.keys()) if (!newMap.has(k)) tagsToRemove.push(k);
8762
8513
  if (tagsToRemove.length > 0) {
8763
- await this.getClient().send(new UntagResourceCommand$12({
8514
+ await this.getClient().send(new UntagResourceCommand$13({
8764
8515
  ResourceARN: arn,
8765
8516
  TagKeys: tagsToRemove
8766
8517
  }));
8767
8518
  this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from WAFv2 WebACL ${arn}`);
8768
8519
  }
8769
8520
  if (tagsToAdd.length > 0) {
8770
- await this.getClient().send(new TagResourceCommand$12({
8521
+ await this.getClient().send(new TagResourceCommand$13({
8771
8522
  ResourceARN: arn,
8772
8523
  Tags: tagsToAdd
8773
8524
  }));
@@ -8830,12 +8581,6 @@ var WAFv2WebACLProvider = class {
8830
8581
  * Lookup order:
8831
8582
  * 1. `--resource <id>=<arn>` override → verify with `GetWebACL` (parses
8832
8583
  * Name/Id/Scope back out of the ARN).
8833
- * 2. Walk `ListWebACLs(Scope)` → match `aws:cdk:path` via
8834
- * `ListTagsForResource(ResourceARN)` (returns
8835
- * `TagInfoForResource.TagList`, standard `Key`/`Value` shape).
8836
- *
8837
- * `Scope` is required by `ListWebACLs` — read from
8838
- * `Properties.Scope` (`REGIONAL` is the default).
8839
8584
  */
8840
8585
  async import(input) {
8841
8586
  if (input.knownPhysicalId) try {
@@ -8853,31 +8598,7 @@ var WAFv2WebACLProvider = class {
8853
8598
  if (err instanceof WAFNonexistentItemException) return null;
8854
8599
  throw err;
8855
8600
  }
8856
- const scope = input.properties["Scope"] || "REGIONAL";
8857
- const match = await importTagWalk({
8858
- cdkPath: input.cdkPath,
8859
- logicalId: input.logicalId,
8860
- listPage: async (marker) => {
8861
- const list = await this.getClient().send(new ListWebACLsCommand({
8862
- Scope: scope,
8863
- ...marker && { NextMarker: marker }
8864
- }));
8865
- return {
8866
- items: list.WebACLs,
8867
- nextMarker: list.NextMarker
8868
- };
8869
- },
8870
- describe: async (item) => {
8871
- if (!item.ARN) return void 0;
8872
- return await this.getClient().send(new ListTagsForResourceCommand$11({ ResourceARN: item.ARN }));
8873
- },
8874
- tagsOf: (tagsResp) => tagsResp.TagInfoForResource?.TagList
8875
- });
8876
- if (!match) return null;
8877
- return {
8878
- physicalId: match.summary.ARN,
8879
- attributes: {}
8880
- };
8601
+ return null;
8881
8602
  }
8882
8603
  };
8883
8604
 
@@ -10900,6 +10621,7 @@ const NON_PROVISIONABLE_TYPES = /* @__PURE__ */ new Set([
10900
10621
  "AWS::AppStream::Fleet",
10901
10622
  "AWS::AppSync::ApiCache",
10902
10623
  "AWS::AutoScalingPlans::ScalingPlan",
10624
+ "AWS::Bedrock::DefaultPromptRouter",
10903
10625
  "AWS::Bedrock::ModelInvocationJob",
10904
10626
  "AWS::BedrockAgentCore::TokenVault",
10905
10627
  "AWS::Cloud9::EnvironmentEC2",
@@ -11343,7 +11065,7 @@ var CloudControlProvider = class {
11343
11065
  this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
11344
11066
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
11345
11067
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
11346
- const { ASGProvider } = await import("./asg-provider-Co34HfIH.js").then((n) => n.n);
11068
+ const { ASGProvider } = await import("./asg-provider-rcNqhfPS.js").then((n) => n.n);
11347
11069
  await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
11348
11070
  return;
11349
11071
  }
@@ -14078,6 +13800,25 @@ const PROPERTY_COVERAGE_BY_TYPE = /* @__PURE__ */ new Map([
14078
13800
  ]),
14079
13801
  silentDrop: /* @__PURE__ */ new Map()
14080
13802
  }],
13803
+ ["AWS::Lambda::MicrovmImage", {
13804
+ handled: /* @__PURE__ */ new Set([
13805
+ "AdditionalOsCapabilities",
13806
+ "BaseImageArn",
13807
+ "BaseImageVersion",
13808
+ "BuildRoleArn",
13809
+ "CodeArtifact",
13810
+ "CpuConfigurations",
13811
+ "Description",
13812
+ "EgressNetworkConnectors",
13813
+ "EnvironmentVariables",
13814
+ "Hooks",
13815
+ "Logging",
13816
+ "Name",
13817
+ "Resources",
13818
+ "Tags"
13819
+ ]),
13820
+ silentDrop: /* @__PURE__ */ new Map()
13821
+ }],
14081
13822
  ["AWS::Lambda::Permission", {
14082
13823
  handled: /* @__PURE__ */ new Set([
14083
13824
  "Action",
@@ -15673,12 +15414,6 @@ var IAMRoleProvider = class {
15673
15414
  * Lookup order:
15674
15415
  * 1. `--resource` override or `Properties.RoleName` → use directly,
15675
15416
  * verify via `GetRole`.
15676
- * 2. `ListRoles` + `ListRoleTags`, match `aws:cdk:path` tag.
15677
- *
15678
- * `ListRoles` is paginated and IAM is global (no region scoping), so this
15679
- * walks every role in the account once. Acceptable for the cardinalities
15680
- * we expect (typically <100 roles per account); larger accounts may want
15681
- * to provide `--resource` overrides instead.
15682
15417
  */
15683
15418
  async import(input) {
15684
15419
  const explicit = resolveExplicitPhysicalId(input, "RoleName");
@@ -15692,32 +15427,7 @@ var IAMRoleProvider = class {
15692
15427
  if (err instanceof NoSuchEntityException) return null;
15693
15428
  throw err;
15694
15429
  }
15695
- const match = await importTagWalk({
15696
- cdkPath: input.cdkPath,
15697
- logicalId: input.logicalId,
15698
- listPage: async (marker) => {
15699
- const list = await this.iamClient.send(new ListRolesCommand({ ...marker && { Marker: marker } }));
15700
- return {
15701
- items: list.Roles,
15702
- nextMarker: list.IsTruncated ? list.Marker : void 0
15703
- };
15704
- },
15705
- describe: async (role) => {
15706
- if (!role.RoleName) return void 0;
15707
- try {
15708
- return await this.iamClient.send(new ListRoleTagsCommand({ RoleName: role.RoleName }));
15709
- } catch (err) {
15710
- if (err instanceof NoSuchEntityException) return void 0;
15711
- throw err;
15712
- }
15713
- },
15714
- tagsOf: (tags) => tags.Tags
15715
- });
15716
- if (!match) return null;
15717
- return {
15718
- physicalId: match.summary.RoleName,
15719
- attributes: {}
15720
- };
15430
+ return null;
15721
15431
  }
15722
15432
  };
15723
15433
  /**
@@ -16213,6 +15923,167 @@ function computeImplicitDeleteEdges(resources) {
16213
15923
  return edges;
16214
15924
  }
16215
15925
 
15926
+ //#endregion
15927
+ //#region src/deployment/retryable-errors.ts
15928
+ /**
15929
+ * Patterns that mark an AWS error as a transient/retryable failure.
15930
+ * Each entry is a substring match against the error message; all of these
15931
+ * are situations where the same call typically succeeds after a short delay
15932
+ * because of eventual consistency or just-created-dependency propagation.
15933
+ */
15934
+ const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
15935
+ "cannot be assumed",
15936
+ "Firehose is unable to assume role",
15937
+ "is unable to assume provided role",
15938
+ "role defined for the function",
15939
+ "not authorized to perform",
15940
+ "execution role",
15941
+ "trust policy",
15942
+ "Role validation failed",
15943
+ "does not have required permissions",
15944
+ "Trusted Entity",
15945
+ "currently in the following state: Pending",
15946
+ "has dependencies and cannot be deleted",
15947
+ "can't be deleted since it has",
15948
+ "DependencyViolation",
15949
+ "does not exist",
15950
+ "Schema is currently being altered",
15951
+ "Invalid principal in policy",
15952
+ "Policy Error: PrincipalNotFound",
15953
+ "Invalid value for the parameter Policy",
15954
+ "required permissions for: ENHANCED_MONITORING",
15955
+ "Caught ServiceAccessDeniedException",
15956
+ "permissions required to assume the role",
15957
+ "authorized to assume the provided role",
15958
+ "conflicting conditional operation",
15959
+ "scheduled for deletion",
15960
+ "Cannot access stream",
15961
+ "Please ensure the role can perform",
15962
+ "KMS key is invalid for CreateGrant",
15963
+ "Policy contains a statement with one or more invalid principals",
15964
+ "Invalid IAM Instance Profile",
15965
+ "Invalid InstanceProfile",
15966
+ "Failed to authorize instance profile",
15967
+ "Could not deliver test message",
15968
+ "wait 60 seconds",
15969
+ "concurrent update operation",
15970
+ "because it is in use",
15971
+ "Rate exceeded"
15972
+ ];
15973
+ /**
15974
+ * HTTP status codes that always indicate a transient failure worth retrying.
15975
+ * 429 = Too Many Requests (throttle), 503 = Service Unavailable.
15976
+ */
15977
+ const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
15978
+ /**
15979
+ * AWS SDK v3 canonical throttling error names. Mirrors
15980
+ * `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
15981
+ * error (or wrapped cause) whose `name` is one of these is a transient rate-
15982
+ * limit rejection worth retrying with backoff. Detecting by NAME is more
15983
+ * robust than by HTTP status because most AWS throttles surface as HTTP 400
15984
+ * (not 429) with the throttling signal carried only in the error code / name
15985
+ * (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
15986
+ */
15987
+ const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
15988
+ "BandwidthLimitExceeded",
15989
+ "EC2ThrottledException",
15990
+ "LimitExceededException",
15991
+ "PriorRequestNotComplete",
15992
+ "ProvisionedThroughputExceededException",
15993
+ "RequestLimitExceeded",
15994
+ "RequestThrottled",
15995
+ "RequestThrottledException",
15996
+ "SlowDown",
15997
+ "ThrottledException",
15998
+ "Throttling",
15999
+ "ThrottlingException",
16000
+ "TooManyRequestsException",
16001
+ "TransactionInProgressException"
16002
+ ]);
16003
+ /**
16004
+ * Walk the error + its `.cause` chain (bounded) looking for a rate-limit
16005
+ * signal — either an AWS SDK v3 throttling error `name`
16006
+ * ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
16007
+ * ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
16008
+ *
16009
+ * cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
16010
+ * typically one cause-link deep; the bounded walk also tolerates SDK errors
16011
+ * that nest a `$response`/cause without exploding on a cyclic chain.
16012
+ *
16013
+ * BOTH signals are checked at EVERY depth. An earlier version checked the name
16014
+ * to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
16015
+ * links deep was missed.
16016
+ */
16017
+ function isThrottlingError(error) {
16018
+ let current = error;
16019
+ for (let depth = 0; depth < 5 && current != null; depth++) {
16020
+ const name = current.name;
16021
+ if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
16022
+ const status = current.$metadata?.httpStatusCode;
16023
+ if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
16024
+ current = current.cause;
16025
+ }
16026
+ return false;
16027
+ }
16028
+ /**
16029
+ * Determine whether an AWS error should be retried.
16030
+ *
16031
+ * Checks (in order):
16032
+ * 1. Rate-limit signal on the error or any wrapped cause — throttling error
16033
+ * `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
16034
+ * 429, so the name check carries most of the weight). See
16035
+ * {@link isThrottlingError}.
16036
+ * 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
16037
+ */
16038
+ function isRetryableTransientError(error, message) {
16039
+ if (isThrottlingError(error)) return true;
16040
+ return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
16041
+ }
16042
+
16043
+ //#endregion
16044
+ //#region src/deployment/retry.ts
16045
+ /**
16046
+ * Retry helper for resource provisioning operations that hit transient
16047
+ * AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
16048
+ * dependency violations, etc.).
16049
+ *
16050
+ * Extracted from DeployEngine so the backoff schedule can be unit-tested
16051
+ * in isolation. The retryable-error classifier itself lives in
16052
+ * `./retryable-errors.ts`.
16053
+ */
16054
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
16055
+ /**
16056
+ * Run `operation`, retrying transient failures with exponential backoff
16057
+ * capped at `maxDelayMs`.
16058
+ *
16059
+ * Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
16060
+ * 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
16061
+ *
16062
+ * Non-retryable errors are rethrown immediately. The transient-error
16063
+ * classifier is `isRetryableTransientError` from ./retryable-errors.ts.
16064
+ */
16065
+ async function withRetry(operation, logicalId, opts = {}) {
16066
+ const maxRetries = opts.maxRetries ?? 8;
16067
+ const initialDelayMs = opts.initialDelayMs ?? 1e3;
16068
+ const maxDelayMs = opts.maxDelayMs ?? 8e3;
16069
+ const sleep = opts.sleep ?? defaultSleep;
16070
+ let lastError;
16071
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
16072
+ return await operation();
16073
+ } catch (error) {
16074
+ lastError = error;
16075
+ const message = error instanceof Error ? error.message : String(error);
16076
+ if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
16077
+ const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
16078
+ opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
16079
+ for (let waited = 0; waited < delay; waited += 1e3) {
16080
+ if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
16081
+ await sleep(Math.min(1e3, delay - waited));
16082
+ }
16083
+ }
16084
+ throw lastError;
16085
+ }
16086
+
16216
16087
  //#endregion
16217
16088
  //#region src/deployment/resource-deadline.ts
16218
16089
  /**
@@ -17860,5 +17731,5 @@ var DeployEngine = class {
17860
17731
  };
17861
17732
 
17862
17733
  //#endregion
17863
- export { AssetModeResolver as $, StackTerminationProtectionError as $t, importTagWalk as A, expectedOwnerParam as At, DagBuilder as B, ConfigError as Bt, slowCcOperationTimeoutMs as C, resolveUseCdkBootstrapAssets as Ct, cfnRefValueFromPhysicalId as D, MIGRATE_TMP_PREFIX as Dt, IntrinsicFunctionResolver as E, CFN_TEMPLATE_URL_LIMIT as Et, normalizeAwsTagsToCfn as F, getAwsClients as Ft, shouldRetainResource as G, LockError as Gt, LockManager as H, LocalInvokeBuildError as Ht, resolveExplicitPhysicalId as I, resetAwsClients as It, WorkGraph as J, PartialFailureError as Jt, AssetPublisher as K, MissingCdkCliError as Kt, assertRegionMatch as L, setAwsClients as Lt, isRetryableTransientError as M, clearBucketRegionCache as Mt, CDK_PATH_TAG as N, resolveBucketRegion as Nt, refStateLookupFromResource as O, findLargeInlineResources as Ot, matchesCdkPath as P, AwsClients as Pt, rewriteTemplateAssetReferences as Q, StackHasActiveImportsError as Qt, applyRoleArnIfSet as R, AssetError as Rt, CloudControlProvider as S, resolveStateBucketWithDefaultAndSource as St, isTerminationProtectionPropagationError as T, CFN_TEMPLATE_BODY_LIMIT as Tt, S3StateBackend as U, LocalMigrateError as Ut, TemplateParser as V, DependencyError as Vt, rebuildClientForBucketRegion as W, LocalStartServiceError as Wt, createAssetRedirectResolver as X, ResourceTimeoutError as Xt, buildAssetRedirectMap as Y, ProvisioningError as Yt, loadPublishableAssetManifest as Z, ResourceUpdateNotSupportedError as Zt, yellow as _, resolveApp as _t, IMPLICIT_DELETE_DEPENDENCIES as a, withErrorHandling as an, validateContainerRepoName as at, ProviderRegistry as b, resolveSkipPrefix as bt, MULTI_REGION_RECREATE_BLOCKED_TYPES as c, getDockerCmd as ct, formatResourceLine as d, AssetManifestLoader as dt, StateError as en, BOOTSTRAP_MARKER_PREFIX as et, bold as f, getDockerImageBySourceHash as ft, red as g, getLegacyStateBucketName as gt, green as h, getDefaultStateBucketName as ht, withResourceDeadline as i, normalizeAwsError as in, validateAssetBucketName as it, withRetry as j, AssemblyReader as jt, WAFv2WebACLProvider as k, uploadCfnTemplate as kt, isStatefulRecreateTargetSync as l, runDockerForeground as lt, gray as m, synthesisStatusMessage as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, formatError as nn, getBootstrapMarkerKey as nt, computeImplicitDeleteEdges as o, __exportAll as on, buildDockerImage as ot, cyan as p, Synthesizer as pt, stringifyValue as q, NestedStackChildDirectDestroyError as qt, DeployEngine as r, isCdkdError as rn, parseBootstrapMarker as rt, extractDeploymentEventError as s, formatDockerLoginError as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, SynthesisError as tn, ensureAssetStorage as tt, renderStatefulReason as u, runDockerStreaming as ut, IAMRoleProvider as v, resolveAutoAssetStorage as vt, disableInstanceApiTermination as w, warnDeprecatedNoPrefixCliFlag as wt, findActionableSilentDrops as x, resolveStateBucketWithDefault as xt, collectInlinePolicyNamesManagedBySiblings as y, resolveCaptureObservedState as yt, DiffCalculator as z, CdkdError as zt };
17864
- //# sourceMappingURL=deploy-engine-0U3bYBq4.js.map
17734
+ export { getBootstrapMarkerKey as $, formatError as $t, refStateLookupFromResource as A, resolveBucketRegion as At, S3StateBackend as B, LocalMigrateError as Bt, findActionableSilentDrops as C, CFN_TEMPLATE_URL_LIMIT as Ct, isTerminationProtectionPropagationError as D, expectedOwnerParam as Dt, disableInstanceApiTermination as E, uploadCfnTemplate as Et, applyRoleArnIfSet as F, AssetError as Ft, WorkGraph as G, PartialFailureError as Gt, shouldRetainResource as H, LockError as Ht, DiffCalculator as I, CdkdError as It, loadPublishableAssetManifest as J, ResourceUpdateNotSupportedError as Jt, buildAssetRedirectMap as K, ProvisioningError as Kt, DagBuilder as L, ConfigError as Lt, normalizeAwsTagsToCfn as M, getAwsClients as Mt, resolveExplicitPhysicalId as N, resetAwsClients as Nt, IntrinsicFunctionResolver as O, AssemblyReader as Ot, assertRegionMatch as P, setAwsClients as Pt, ensureAssetStorage as Q, SynthesisError as Qt, TemplateParser as R, DependencyError as Rt, ProviderRegistry as S, CFN_TEMPLATE_BODY_LIMIT as St, slowCcOperationTimeoutMs as T, findLargeInlineResources as Tt, AssetPublisher as U, MissingCdkCliError as Ut, rebuildClientForBucketRegion as V, LocalStartServiceError as Vt, stringifyValue as W, NestedStackChildDirectDestroyError as Wt, AssetModeResolver as X, StackTerminationProtectionError as Xt, rewriteTemplateAssetReferences as Y, StackHasActiveImportsError as Yt, BOOTSTRAP_MARKER_PREFIX as Z, StateError as Zt, green as _, resolveSkipPrefix as _t, withRetry as a, getDockerCmd as at, IAMRoleProvider as b, resolveUseCdkBootstrapAssets as bt, computeImplicitDeleteEdges as c, AssetManifestLoader as ct, isStatefulRecreateTargetSync as d, synthesisStatusMessage as dt, isCdkdError as en, parseBootstrapMarker as et, renderStatefulReason as f, getDefaultStateBucketName as ft, gray as g, resolveCaptureObservedState as gt, cyan as h, resolveAutoAssetStorage as ht, withResourceDeadline as i, formatDockerLoginError as it, WAFv2WebACLProvider as j, AwsClients as jt, cfnRefValueFromPhysicalId as k, clearBucketRegionCache as kt, extractDeploymentEventError as l, getDockerImageBySourceHash as lt, bold as m, resolveApp as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, withErrorHandling as nn, validateContainerRepoName as nt, isRetryableTransientError as o, runDockerForeground as ot, formatResourceLine as p, getLegacyStateBucketName as pt, createAssetRedirectResolver as q, ResourceTimeoutError as qt, DeployEngine as r, __exportAll as rn, buildDockerImage as rt, IMPLICIT_DELETE_DEPENDENCIES as s, runDockerStreaming as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, normalizeAwsError as tn, validateAssetBucketName as tt, MULTI_REGION_RECREATE_BLOCKED_TYPES as u, Synthesizer as ut, red as v, resolveStateBucketWithDefault as vt, CloudControlProvider as w, MIGRATE_TMP_PREFIX as wt, collectInlinePolicyNamesManagedBySiblings as x, warnDeprecatedNoPrefixCliFlag as xt, yellow as y, resolveStateBucketWithDefaultAndSource as yt, LockManager as z, LocalInvokeBuildError as zt };
17735
+ //# sourceMappingURL=deploy-engine-Dg51mHCV.js.map