@go-to-k/cdkd 0.254.0 → 0.255.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.
@@ -10878,7 +10878,7 @@ var CloudControlProvider = class {
10878
10878
  this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
10879
10879
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
10880
10880
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
10881
- const { ASGProvider } = await import("./asg-provider-BgcH5ymP.js").then((n) => n.n);
10881
+ const { ASGProvider } = await import("./asg-provider-BPtdM67j.js").then((n) => n.n);
10882
10882
  await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
10883
10883
  return;
10884
10884
  }
@@ -14677,6 +14677,319 @@ function isCustomResource(resourceType) {
14677
14677
  return resourceType.startsWith("Custom::") || resourceType === "AWS::CloudFormation::CustomResource";
14678
14678
  }
14679
14679
 
14680
+ //#endregion
14681
+ //#region src/deployment/retryable-errors.ts
14682
+ /**
14683
+ * Patterns that mark an AWS error as a transient/retryable failure.
14684
+ * Each entry is a substring match against the error message; all of these
14685
+ * are situations where the same call typically succeeds after a short delay
14686
+ * because of eventual consistency or just-created-dependency propagation.
14687
+ */
14688
+ const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
14689
+ "cannot be assumed",
14690
+ "Firehose is unable to assume role",
14691
+ "is unable to assume provided role",
14692
+ "role defined for the function",
14693
+ "not authorized to perform",
14694
+ "execution role",
14695
+ "trust policy",
14696
+ "Role validation failed",
14697
+ "does not have required permissions",
14698
+ "Trusted Entity",
14699
+ "currently in the following state: Pending",
14700
+ "has dependencies and cannot be deleted",
14701
+ "can't be deleted since it has",
14702
+ "DependencyViolation",
14703
+ "does not exist",
14704
+ "Schema is currently being altered",
14705
+ "Invalid principal in policy",
14706
+ "Policy Error: PrincipalNotFound",
14707
+ "Invalid value for the parameter Policy",
14708
+ "required permissions for: ENHANCED_MONITORING",
14709
+ "Caught ServiceAccessDeniedException",
14710
+ "permissions required to assume the role",
14711
+ "authorized to assume the provided role",
14712
+ "conflicting conditional operation",
14713
+ "scheduled for deletion",
14714
+ "Cannot access stream",
14715
+ "Please ensure the role can perform",
14716
+ "KMS key is invalid for CreateGrant",
14717
+ "Policy contains a statement with one or more invalid principals",
14718
+ "Invalid IAM Instance Profile",
14719
+ "Invalid InstanceProfile",
14720
+ "Failed to authorize instance profile",
14721
+ "Could not deliver test message",
14722
+ "wait 60 seconds",
14723
+ "concurrent update operation",
14724
+ "because it is in use",
14725
+ "Rate exceeded"
14726
+ ];
14727
+ /**
14728
+ * HTTP status codes that always indicate a transient failure worth retrying.
14729
+ * 429 = Too Many Requests (throttle), 503 = Service Unavailable.
14730
+ */
14731
+ const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
14732
+ /**
14733
+ * AWS SDK v3 canonical throttling error names. Mirrors
14734
+ * `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
14735
+ * error (or wrapped cause) whose `name` is one of these is a transient rate-
14736
+ * limit rejection worth retrying with backoff. Detecting by NAME is more
14737
+ * robust than by HTTP status because most AWS throttles surface as HTTP 400
14738
+ * (not 429) with the throttling signal carried only in the error code / name
14739
+ * (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
14740
+ */
14741
+ const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
14742
+ "BandwidthLimitExceeded",
14743
+ "EC2ThrottledException",
14744
+ "LimitExceededException",
14745
+ "PriorRequestNotComplete",
14746
+ "ProvisionedThroughputExceededException",
14747
+ "RequestLimitExceeded",
14748
+ "RequestThrottled",
14749
+ "RequestThrottledException",
14750
+ "SlowDown",
14751
+ "ThrottledException",
14752
+ "Throttling",
14753
+ "ThrottlingException",
14754
+ "TooManyRequestsException",
14755
+ "TransactionInProgressException"
14756
+ ]);
14757
+ /**
14758
+ * Walk the error + its `.cause` chain (bounded) looking for a rate-limit
14759
+ * signal — either an AWS SDK v3 throttling error `name`
14760
+ * ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
14761
+ * ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
14762
+ *
14763
+ * cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
14764
+ * typically one cause-link deep; the bounded walk also tolerates SDK errors
14765
+ * that nest a `$response`/cause without exploding on a cyclic chain.
14766
+ *
14767
+ * BOTH signals are checked at EVERY depth. An earlier version checked the name
14768
+ * to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
14769
+ * links deep was missed. This is the single shared cause-walk: the read-only
14770
+ * import tag walk (`src/provisioning/import-tag-walk.ts`) composes its own
14771
+ * classifier on top of this function rather than re-implementing the traversal,
14772
+ * so the two cannot drift apart again.
14773
+ */
14774
+ function isThrottlingError(error) {
14775
+ let current = error;
14776
+ for (let depth = 0; depth < 5 && current != null; depth++) {
14777
+ const name = current.name;
14778
+ if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
14779
+ const status = current.$metadata?.httpStatusCode;
14780
+ if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
14781
+ current = current.cause;
14782
+ }
14783
+ return false;
14784
+ }
14785
+ /**
14786
+ * Determine whether an AWS error should be retried.
14787
+ *
14788
+ * Checks (in order):
14789
+ * 1. Rate-limit signal on the error or any wrapped cause — throttling error
14790
+ * `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
14791
+ * 429, so the name check carries most of the weight). See
14792
+ * {@link isThrottlingError}.
14793
+ * 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
14794
+ */
14795
+ function isRetryableTransientError(error, message) {
14796
+ if (isThrottlingError(error)) return true;
14797
+ return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
14798
+ }
14799
+
14800
+ //#endregion
14801
+ //#region src/deployment/retry.ts
14802
+ /**
14803
+ * Retry helper for resource provisioning operations that hit transient
14804
+ * AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
14805
+ * dependency violations, etc.).
14806
+ *
14807
+ * Extracted from DeployEngine so the backoff schedule can be unit-tested
14808
+ * in isolation. The retryable-error classifier itself lives in
14809
+ * `./retryable-errors.ts`.
14810
+ */
14811
+ const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
14812
+ /**
14813
+ * Run `operation`, retrying transient failures with exponential backoff
14814
+ * capped at `maxDelayMs`.
14815
+ *
14816
+ * Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
14817
+ * 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
14818
+ *
14819
+ * Non-retryable errors are rethrown immediately. The transient-error
14820
+ * classifier is `isRetryableTransientError` from ./retryable-errors.ts.
14821
+ */
14822
+ async function withRetry(operation, logicalId, opts = {}) {
14823
+ const maxRetries = opts.maxRetries ?? 8;
14824
+ const initialDelayMs = opts.initialDelayMs ?? 1e3;
14825
+ const maxDelayMs = opts.maxDelayMs ?? 8e3;
14826
+ const sleep = opts.sleep ?? defaultSleep;
14827
+ let lastError;
14828
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
14829
+ return await operation();
14830
+ } catch (error) {
14831
+ lastError = error;
14832
+ const message = error instanceof Error ? error.message : String(error);
14833
+ if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
14834
+ const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
14835
+ opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
14836
+ for (let waited = 0; waited < delay; waited += 1e3) {
14837
+ if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
14838
+ await sleep(Math.min(1e3, delay - waited));
14839
+ }
14840
+ }
14841
+ throw lastError;
14842
+ }
14843
+
14844
+ //#endregion
14845
+ //#region src/provisioning/import-tag-walk.ts
14846
+ /**
14847
+ * Shared, throttle-tolerant `aws:cdk:path` tag walk for `ResourceProvider.import`.
14848
+ *
14849
+ * Providers that adopt a resource without an explicit name property fall back
14850
+ * to step 3 of the lookup order documented in `./import-helpers.ts`: enumerate
14851
+ * the service's `List*`/`Describe*` pages, then issue ONE per-candidate read
14852
+ * (`DescribeX` / `ListTagsForResource`) to obtain the tag set — the list
14853
+ * summaries usually do not carry tags. That is an inherent **N+1** read
14854
+ * pattern: an account with many resources of the type produces one API call
14855
+ * per candidate in a tight loop, which is exactly the shape AWS rate-limits.
14856
+ *
14857
+ * Every provider previously hand-rolled this loop with NO backoff, so a single
14858
+ * throttled `Describe*` aborted the whole `cdkd import` run. This helper
14859
+ * centralises the loop and wraps BOTH the list page fetch and the per-candidate
14860
+ * describe in the deploy engine's `withRetry` with exponential backoff.
14861
+ *
14862
+ * ## Why a narrower classifier than `isRetryableTransientError`
14863
+ *
14864
+ * The deploy engine's classifier is tuned for the WRITE path: it also treats
14865
+ * eventual-consistency phrasings like `does not exist` / `not authorized to
14866
+ * perform` as transient, because a just-created dependency legitimately needs a
14867
+ * moment to propagate. On a read-only import walk those messages mean the
14868
+ * opposite — the candidate really is gone, or the caller's credentials really
14869
+ * lack the permission — and retrying them burns the full backoff budget per
14870
+ * candidate before surfacing the true error.
14871
+ *
14872
+ * So the walk retries throttling ONLY, reusing the deploy engine's throttle
14873
+ * tables verbatim ({@link THROTTLING_ERROR_NAMES} /
14874
+ * {@link RETRYABLE_HTTP_STATUS_CODES}) rather than maintaining a second copy.
14875
+ *
14876
+ * ## Batching
14877
+ *
14878
+ * Batched tag reads are deliberately NOT modelled here: the services this
14879
+ * helper currently serves (EMR `DescribeCluster`, DocDB
14880
+ * `ListTagsForResource`) expose only single-resource reads. A service that
14881
+ * genuinely offers a batch API (e.g. CodeCommit `BatchGetRepositories`) can
14882
+ * satisfy several candidates from one call inside its own `describe` callback,
14883
+ * or bypass the helper entirely.
14884
+ */
14885
+ /** Max number of retries after the first attempt, per API call in the walk. */
14886
+ const DEFAULT_MAX_RETRIES = 5;
14887
+ /** Initial backoff; each retry doubles it up to {@link DEFAULT_MAX_DELAY_MS}. */
14888
+ const DEFAULT_INITIAL_DELAY_MS = 500;
14889
+ /** Cap for the per-retry delay (0.5s -> 1s -> 2s -> 4s -> 5s, ~12.5s total). */
14890
+ const DEFAULT_MAX_DELAY_MS = 5e3;
14891
+ /**
14892
+ * Wall-clock ceiling for the WHOLE walk (all pages + all candidates), not just
14893
+ * one call. Backoff is per-call, so without this a sustained throttle against a
14894
+ * large account degrades into `(pages + candidates) x ~12.5s` of near-silent
14895
+ * retrying — ~42 minutes for 200 candidates, with no way to tell a slow walk
14896
+ * from a hung one. 10 minutes is generous for a healthy walk (a 200-candidate
14897
+ * DocDB account completes in seconds when AWS is not throttling) and bounds the
14898
+ * pathological case to something a user will wait through.
14899
+ */
14900
+ const DEFAULT_MAX_WALK_MS = 10 * 6e4;
14901
+ /**
14902
+ * Ceiling on pages fetched. A service that returns a non-advancing pagination
14903
+ * token (a bug, or a marker cdkd echoes back wrongly) would otherwise spin
14904
+ * forever. This is now the shared path every migrated provider runs on, so the
14905
+ * guard belongs here rather than in each caller.
14906
+ */
14907
+ const DEFAULT_MAX_PAGES = 1e3;
14908
+ /**
14909
+ * Whether an error hit during the read-only tag walk is a rate-limit rejection
14910
+ * worth backing off on.
14911
+ *
14912
+ * Delegates the error + `.cause` chain traversal to the deploy engine's
14913
+ * {@link isThrottlingError} (throttling error names + retryable HTTP statuses,
14914
+ * at every cause depth) and adds the canonical `Rate exceeded` message, which
14915
+ * several services return with an HTTP 400 and a service-specific error name.
14916
+ *
14917
+ * This is deliberately NARROWER than `isRetryableTransientError`: that
14918
+ * classifier also treats write-path eventual-consistency phrasings (`does not
14919
+ * exist`, `not authorized to perform`) as transient, because a just-created
14920
+ * dependency legitimately needs a moment to propagate. On a read-only walk
14921
+ * those mean the candidate really is gone or the credentials really lack the
14922
+ * permission, and retrying them burns the full backoff budget per candidate
14923
+ * before surfacing the true error.
14924
+ *
14925
+ * Exported for direct unit testing and for providers whose tag walk cannot use
14926
+ * {@link importTagWalk} verbatim.
14927
+ */
14928
+ function isThrottlingLikeError(error, message) {
14929
+ return isThrottlingError(error) || message.includes("Rate exceeded");
14930
+ }
14931
+ /** Thrown when the walk exceeds its wall-clock budget or page ceiling. */
14932
+ var ImportTagWalkLimitError = class extends Error {
14933
+ constructor(message) {
14934
+ super(message);
14935
+ this.name = "ImportTagWalkLimitError";
14936
+ }
14937
+ };
14938
+ /**
14939
+ * Paginate `listPage`, `describe` each candidate, and return the first one
14940
+ * whose tags carry the requested `aws:cdk:path`. Returns `null` when no
14941
+ * candidate matches (or when `cdkPath` is empty).
14942
+ *
14943
+ * Both callbacks are individually retried with exponential backoff on
14944
+ * throttling errors, so one rate-limited call mid-walk no longer aborts the
14945
+ * whole import.
14946
+ */
14947
+ async function importTagWalk(options) {
14948
+ const { cdkPath, listPage, describe, tagsOf } = options;
14949
+ if (!cdkPath) return null;
14950
+ const logicalId = options.logicalId ?? "import";
14951
+ const logger = options.retry?.logger ?? getLogger();
14952
+ const maxWalkMs = options.retry?.maxWalkMs ?? DEFAULT_MAX_WALK_MS;
14953
+ const maxPages = options.retry?.maxPages ?? DEFAULT_MAX_PAGES;
14954
+ const retryOpts = {
14955
+ maxRetries: options.retry?.maxRetries ?? DEFAULT_MAX_RETRIES,
14956
+ initialDelayMs: options.retry?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS,
14957
+ maxDelayMs: options.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS,
14958
+ isRetryable: (message, error) => isThrottlingLikeError(error, message),
14959
+ logger,
14960
+ ...options.retry?.isInterrupted && { isInterrupted: options.retry.isInterrupted },
14961
+ ...options.retry?.onInterrupted && { onInterrupted: options.retry.onInterrupted },
14962
+ ...options.retry?.sleep && { sleep: options.retry.sleep }
14963
+ };
14964
+ const startedAt = Date.now();
14965
+ const assertWithinBudget = (stage) => {
14966
+ const elapsed = Date.now() - startedAt;
14967
+ 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>.`);
14968
+ };
14969
+ let marker;
14970
+ let pages = 0;
14971
+ do {
14972
+ assertWithinBudget("listing candidates");
14973
+ 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>.`);
14974
+ const currentMarker = marker;
14975
+ const page = await withRetry(() => listPage(currentMarker), logicalId, retryOpts);
14976
+ for (const summary of page.items ?? []) {
14977
+ assertWithinBudget("reading candidate tags");
14978
+ const detail = await withRetry(() => describe(summary), logicalId, retryOpts);
14979
+ if (detail === void 0) {
14980
+ logger.debug(` ↷ Skipped an ${logicalId} import candidate: its detail lookup returned no result`);
14981
+ continue;
14982
+ }
14983
+ if (matchesCdkPath(tagsOf(detail, summary), cdkPath)) return {
14984
+ summary,
14985
+ detail
14986
+ };
14987
+ }
14988
+ marker = page.nextMarker;
14989
+ } while (marker);
14990
+ return null;
14991
+ }
14992
+
14680
14993
  //#endregion
14681
14994
  //#region src/provisioning/providers/iam-role-provider.ts
14682
14995
  /**
@@ -15226,25 +15539,32 @@ var IAMRoleProvider = class {
15226
15539
  if (err instanceof NoSuchEntityException) return null;
15227
15540
  throw err;
15228
15541
  }
15229
- if (!input.cdkPath) return null;
15230
- let marker;
15231
- do {
15232
- const list = await this.iamClient.send(new ListRolesCommand({ ...marker && { Marker: marker } }));
15233
- for (const role of list.Roles ?? []) {
15234
- if (!role.RoleName) continue;
15542
+ const match = await importTagWalk({
15543
+ cdkPath: input.cdkPath,
15544
+ logicalId: input.logicalId,
15545
+ listPage: async (marker) => {
15546
+ const list = await this.iamClient.send(new ListRolesCommand({ ...marker && { Marker: marker } }));
15547
+ return {
15548
+ items: list.Roles,
15549
+ nextMarker: list.IsTruncated ? list.Marker : void 0
15550
+ };
15551
+ },
15552
+ describe: async (role) => {
15553
+ if (!role.RoleName) return void 0;
15235
15554
  try {
15236
- if (matchesCdkPath((await this.iamClient.send(new ListRoleTagsCommand({ RoleName: role.RoleName }))).Tags, input.cdkPath)) return {
15237
- physicalId: role.RoleName,
15238
- attributes: {}
15239
- };
15555
+ return await this.iamClient.send(new ListRoleTagsCommand({ RoleName: role.RoleName }));
15240
15556
  } catch (err) {
15241
- if (err instanceof NoSuchEntityException) continue;
15557
+ if (err instanceof NoSuchEntityException) return void 0;
15242
15558
  throw err;
15243
15559
  }
15244
- }
15245
- marker = list.IsTruncated ? list.Marker : void 0;
15246
- } while (marker);
15247
- return null;
15560
+ },
15561
+ tagsOf: (tags) => tags.Tags
15562
+ });
15563
+ if (!match) return null;
15564
+ return {
15565
+ physicalId: match.summary.RoleName,
15566
+ attributes: {}
15567
+ };
15248
15568
  }
15249
15569
  };
15250
15570
  /**
@@ -15740,170 +16060,6 @@ function computeImplicitDeleteEdges(resources) {
15740
16060
  return edges;
15741
16061
  }
15742
16062
 
15743
- //#endregion
15744
- //#region src/deployment/retryable-errors.ts
15745
- /**
15746
- * Patterns that mark an AWS error as a transient/retryable failure.
15747
- * Each entry is a substring match against the error message; all of these
15748
- * are situations where the same call typically succeeds after a short delay
15749
- * because of eventual consistency or just-created-dependency propagation.
15750
- */
15751
- const RETRYABLE_ERROR_MESSAGE_PATTERNS = [
15752
- "cannot be assumed",
15753
- "Firehose is unable to assume role",
15754
- "is unable to assume provided role",
15755
- "role defined for the function",
15756
- "not authorized to perform",
15757
- "execution role",
15758
- "trust policy",
15759
- "Role validation failed",
15760
- "does not have required permissions",
15761
- "Trusted Entity",
15762
- "currently in the following state: Pending",
15763
- "has dependencies and cannot be deleted",
15764
- "can't be deleted since it has",
15765
- "DependencyViolation",
15766
- "does not exist",
15767
- "Schema is currently being altered",
15768
- "Invalid principal in policy",
15769
- "Policy Error: PrincipalNotFound",
15770
- "Invalid value for the parameter Policy",
15771
- "required permissions for: ENHANCED_MONITORING",
15772
- "Caught ServiceAccessDeniedException",
15773
- "permissions required to assume the role",
15774
- "authorized to assume the provided role",
15775
- "conflicting conditional operation",
15776
- "scheduled for deletion",
15777
- "Cannot access stream",
15778
- "Please ensure the role can perform",
15779
- "KMS key is invalid for CreateGrant",
15780
- "Policy contains a statement with one or more invalid principals",
15781
- "Invalid IAM Instance Profile",
15782
- "Invalid InstanceProfile",
15783
- "Failed to authorize instance profile",
15784
- "Could not deliver test message",
15785
- "wait 60 seconds",
15786
- "concurrent update operation",
15787
- "because it is in use",
15788
- "Rate exceeded"
15789
- ];
15790
- /**
15791
- * HTTP status codes that always indicate a transient failure worth retrying.
15792
- * 429 = Too Many Requests (throttle), 503 = Service Unavailable.
15793
- */
15794
- const RETRYABLE_HTTP_STATUS_CODES = /* @__PURE__ */ new Set([429, 503]);
15795
- /**
15796
- * AWS SDK v3 canonical throttling error names. Mirrors
15797
- * `@aws-sdk/service-error-classification`'s `THROTTLING_ERROR_CODES` — any
15798
- * error (or wrapped cause) whose `name` is one of these is a transient rate-
15799
- * limit rejection worth retrying with backoff. Detecting by NAME is more
15800
- * robust than by HTTP status because most AWS throttles surface as HTTP 400
15801
- * (not 429) with the throttling signal carried only in the error code / name
15802
- * (e.g. SSM `ThrottlingException` for the `Rate exceeded` message).
15803
- */
15804
- const THROTTLING_ERROR_NAMES = /* @__PURE__ */ new Set([
15805
- "BandwidthLimitExceeded",
15806
- "EC2ThrottledException",
15807
- "LimitExceededException",
15808
- "PriorRequestNotComplete",
15809
- "ProvisionedThroughputExceededException",
15810
- "RequestLimitExceeded",
15811
- "RequestThrottled",
15812
- "RequestThrottledException",
15813
- "SlowDown",
15814
- "ThrottledException",
15815
- "Throttling",
15816
- "ThrottlingException",
15817
- "TooManyRequestsException",
15818
- "TransactionInProgressException"
15819
- ]);
15820
- /**
15821
- * Walk the error + its `.cause` chain (bounded) looking for a rate-limit
15822
- * signal — either an AWS SDK v3 throttling error `name`
15823
- * ({@link THROTTLING_ERROR_NAMES}) or a retryable HTTP status
15824
- * ({@link RETRYABLE_HTTP_STATUS_CODES}) on `$metadata`.
15825
- *
15826
- * cdkd wraps the original AWS error in a `ProvisioningError`, so the signal is
15827
- * typically one cause-link deep; the bounded walk also tolerates SDK errors
15828
- * that nest a `$response`/cause without exploding on a cyclic chain.
15829
- *
15830
- * BOTH signals are checked at EVERY depth. An earlier version checked the name
15831
- * to depth 5 but the HTTP status only at depths 0 and 1, so a 429 nested two
15832
- * links deep was missed. This is the single shared cause-walk: the read-only
15833
- * import tag walk (`src/provisioning/import-tag-walk.ts`) composes its own
15834
- * classifier on top of this function rather than re-implementing the traversal,
15835
- * so the two cannot drift apart again.
15836
- */
15837
- function isThrottlingError(error) {
15838
- let current = error;
15839
- for (let depth = 0; depth < 5 && current != null; depth++) {
15840
- const name = current.name;
15841
- if (typeof name === "string" && THROTTLING_ERROR_NAMES.has(name)) return true;
15842
- const status = current.$metadata?.httpStatusCode;
15843
- if (status !== void 0 && RETRYABLE_HTTP_STATUS_CODES.has(status)) return true;
15844
- current = current.cause;
15845
- }
15846
- return false;
15847
- }
15848
- /**
15849
- * Determine whether an AWS error should be retried.
15850
- *
15851
- * Checks (in order):
15852
- * 1. Rate-limit signal on the error or any wrapped cause — throttling error
15853
- * `name` or retryable HTTP status (most AWS throttles are HTTP 400, not
15854
- * 429, so the name check carries most of the weight). See
15855
- * {@link isThrottlingError}.
15856
- * 2. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}
15857
- */
15858
- function isRetryableTransientError(error, message) {
15859
- if (isThrottlingError(error)) return true;
15860
- return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));
15861
- }
15862
-
15863
- //#endregion
15864
- //#region src/deployment/retry.ts
15865
- /**
15866
- * Retry helper for resource provisioning operations that hit transient
15867
- * AWS eventual-consistency errors (IAM propagation, Lambda Pending state,
15868
- * dependency violations, etc.).
15869
- *
15870
- * Extracted from DeployEngine so the backoff schedule can be unit-tested
15871
- * in isolation. The retryable-error classifier itself lives in
15872
- * `./retryable-errors.ts`.
15873
- */
15874
- const defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
15875
- /**
15876
- * Run `operation`, retrying transient failures with exponential backoff
15877
- * capped at `maxDelayMs`.
15878
- *
15879
- * Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):
15880
- * 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)
15881
- *
15882
- * Non-retryable errors are rethrown immediately. The transient-error
15883
- * classifier is `isRetryableTransientError` from ./retryable-errors.ts.
15884
- */
15885
- async function withRetry(operation, logicalId, opts = {}) {
15886
- const maxRetries = opts.maxRetries ?? 8;
15887
- const initialDelayMs = opts.initialDelayMs ?? 1e3;
15888
- const maxDelayMs = opts.maxDelayMs ?? 8e3;
15889
- const sleep = opts.sleep ?? defaultSleep;
15890
- let lastError;
15891
- for (let attempt = 0; attempt <= maxRetries; attempt++) try {
15892
- return await operation();
15893
- } catch (error) {
15894
- lastError = error;
15895
- const message = error instanceof Error ? error.message : String(error);
15896
- if (!(opts.isRetryable ? opts.isRetryable(message, error) : isRetryableTransientError(error, message)) || attempt >= maxRetries) throw error;
15897
- const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
15898
- opts.logger?.debug(` ⏳ Retrying ${logicalId} in ${delay / 1e3}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`);
15899
- for (let waited = 0; waited < delay; waited += 1e3) {
15900
- if (opts.isInterrupted?.()) throw opts.onInterrupted ? opts.onInterrupted() : /* @__PURE__ */ new Error("Interrupted");
15901
- await sleep(Math.min(1e3, delay - waited));
15902
- }
15903
- }
15904
- throw lastError;
15905
- }
15906
-
15907
16063
  //#endregion
15908
16064
  //#region src/deployment/resource-deadline.ts
15909
16065
  /**
@@ -17486,5 +17642,5 @@ var DeployEngine = class {
17486
17642
  };
17487
17643
 
17488
17644
  //#endregion
17489
- export { BOOTSTRAP_MARKER_PREFIX as $, StateError as $t, refStateLookupFromResource as A, AssemblyReader as At, TemplateParser as B, DependencyError as Bt, ProviderRegistry as C, warnDeprecatedNoPrefixCliFlag as Ct, isTerminationProtectionPropagationError as D, findLargeInlineResources as Dt, disableInstanceApiTermination as E, MIGRATE_TMP_PREFIX as Et, resolveExplicitPhysicalId as F, resetAwsClients as Ft, AssetPublisher as G, MissingCdkCliError as Gt, S3StateBackend as H, LocalMigrateError as Ht, assertRegionMatch as I, setAwsClients as It, buildAssetRedirectMap as J, ProvisioningError as Jt, stringifyValue as K, NestedStackChildDirectDestroyError as Kt, applyRoleArnIfSet as L, AssetError as Lt, CDK_PATH_TAG as M, resolveBucketRegion as Mt, matchesCdkPath as N, AwsClients as Nt, IntrinsicFunctionResolver as O, uploadCfnTemplate as Ot, normalizeAwsTagsToCfn as P, getAwsClients as Pt, AssetModeResolver as Q, StackTerminationProtectionError as Qt, DiffCalculator as R, CdkdError as Rt, collectInlinePolicyNamesManagedBySiblings as S, resolveUseCdkBootstrapAssets as St, CloudControlProvider as T, CFN_TEMPLATE_URL_LIMIT as Tt, rebuildClientForBucketRegion as U, LocalStartServiceError as Ut, LockManager as V, LocalInvokeBuildError as Vt, shouldRetainResource as W, LockError as Wt, loadPublishableAssetManifest as X, ResourceUpdateNotSupportedError as Xt, createAssetRedirectResolver as Y, ResourceTimeoutError as Yt, rewriteTemplateAssetReferences as Z, StackHasActiveImportsError as Zt, gray as _, resolveAutoAssetStorage as _t, withRetry as a, __exportAll as an, buildDockerImage as at, yellow as b, resolveStateBucketWithDefault as bt, IMPLICIT_DELETE_DEPENDENCIES as c, runDockerForeground as ct, MULTI_REGION_RECREATE_BLOCKED_TYPES as d, getDockerImageBySourceHash as dt, SynthesisError as en, ensureAssetStorage as et, isStatefulRecreateTargetSync as f, Synthesizer as ft, cyan as g, resolveApp as gt, bold as h, getLegacyStateBucketName as ht, withResourceDeadline as i, withErrorHandling as in, validateContainerRepoName as it, WAFv2WebACLProvider as j, clearBucketRegionCache as jt, cfnRefValueFromPhysicalId as k, expectedOwnerParam as kt, computeImplicitDeleteEdges as l, runDockerStreaming as lt, formatResourceLine as m, getDefaultStateBucketName as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isCdkdError as nn, parseBootstrapMarker as nt, isRetryableTransientError as o, formatDockerLoginError as ot, renderStatefulReason as p, synthesisStatusMessage as pt, WorkGraph as q, PartialFailureError as qt, DeployEngine as r, normalizeAwsError as rn, validateAssetBucketName as rt, isThrottlingError as s, getDockerCmd as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, formatError as tn, getBootstrapMarkerKey as tt, extractDeploymentEventError as u, AssetManifestLoader as ut, green as v, resolveCaptureObservedState as vt, findActionableSilentDrops as w, CFN_TEMPLATE_BODY_LIMIT as wt, IAMRoleProvider as x, resolveStateBucketWithDefaultAndSource as xt, red as y, resolveSkipPrefix as yt, DagBuilder as z, ConfigError as zt };
17490
- //# sourceMappingURL=deploy-engine-d-cwg8zS.js.map
17645
+ export { BOOTSTRAP_MARKER_PREFIX as $, StateError as $t, refStateLookupFromResource as A, AssemblyReader as At, TemplateParser as B, DependencyError as Bt, ProviderRegistry as C, warnDeprecatedNoPrefixCliFlag as Ct, isTerminationProtectionPropagationError as D, findLargeInlineResources as Dt, disableInstanceApiTermination as E, MIGRATE_TMP_PREFIX as Et, resolveExplicitPhysicalId as F, resetAwsClients as Ft, AssetPublisher as G, MissingCdkCliError as Gt, S3StateBackend as H, LocalMigrateError as Ht, assertRegionMatch as I, setAwsClients as It, buildAssetRedirectMap as J, ProvisioningError as Jt, stringifyValue as K, NestedStackChildDirectDestroyError as Kt, applyRoleArnIfSet as L, AssetError as Lt, CDK_PATH_TAG as M, resolveBucketRegion as Mt, matchesCdkPath as N, AwsClients as Nt, IntrinsicFunctionResolver as O, uploadCfnTemplate as Ot, normalizeAwsTagsToCfn as P, getAwsClients as Pt, AssetModeResolver as Q, StackTerminationProtectionError as Qt, DiffCalculator as R, CdkdError as Rt, isRetryableTransientError as S, resolveUseCdkBootstrapAssets as St, CloudControlProvider as T, CFN_TEMPLATE_URL_LIMIT as Tt, rebuildClientForBucketRegion as U, LocalStartServiceError as Ut, LockManager as V, LocalInvokeBuildError as Vt, shouldRetainResource as W, LockError as Wt, loadPublishableAssetManifest as X, ResourceUpdateNotSupportedError as Xt, createAssetRedirectResolver as Y, ResourceTimeoutError as Yt, rewriteTemplateAssetReferences as Z, StackHasActiveImportsError as Zt, yellow as _, resolveAutoAssetStorage as _t, IMPLICIT_DELETE_DEPENDENCIES as a, __exportAll as an, buildDockerImage as at, importTagWalk as b, resolveStateBucketWithDefault as bt, MULTI_REGION_RECREATE_BLOCKED_TYPES as c, runDockerForeground as ct, formatResourceLine as d, getDockerImageBySourceHash as dt, SynthesisError as en, ensureAssetStorage as et, bold as f, Synthesizer as ft, red as g, resolveApp as gt, green as h, getLegacyStateBucketName as ht, withResourceDeadline as i, withErrorHandling as in, validateContainerRepoName as it, WAFv2WebACLProvider as j, clearBucketRegionCache as jt, cfnRefValueFromPhysicalId as k, expectedOwnerParam as kt, isStatefulRecreateTargetSync as l, runDockerStreaming as lt, gray as m, getDefaultStateBucketName as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, isCdkdError as nn, parseBootstrapMarker as nt, computeImplicitDeleteEdges as o, formatDockerLoginError as ot, cyan as p, synthesisStatusMessage as pt, WorkGraph as q, PartialFailureError as qt, DeployEngine as r, normalizeAwsError as rn, validateAssetBucketName as rt, extractDeploymentEventError as s, getDockerCmd as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, formatError as tn, getBootstrapMarkerKey as tt, renderStatefulReason as u, AssetManifestLoader as ut, IAMRoleProvider as v, resolveCaptureObservedState as vt, findActionableSilentDrops as w, CFN_TEMPLATE_BODY_LIMIT as wt, withRetry as x, resolveStateBucketWithDefaultAndSource as xt, collectInlinePolicyNamesManagedBySiblings as y, resolveSkipPrefix as yt, DagBuilder as z, ConfigError as zt };
17646
+ //# sourceMappingURL=deploy-engine-CFUNbwD1.js.map