@go-to-k/cdkd 0.284.23 → 0.284.25

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.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as disableInstanceApiTermination, $n as NestedStackChildDirectDestroyError, $t as ensureAssetStorage, A as isStatefulRecreateTargetSync, An as uploadCfnTemplate, At as s3BucketWebsiteUrl, B as collectPublishedOutputNames, Bn as getAwsClients, Bt as rebuildClientForBucketRegion, C as createPreDeleteFinalSnapshot, Cn as resolveUseCdkBootstrapAssets, Ct as maskSecretsInText, D as makeCanonicalizePropertiesFn, Dn as CFN_TEMPLATE_URL_LIMIT, Dt as s3BucketDomainName, E as unsupportedFinalSnapshotError, En as CFN_TEMPLATE_BODY_LIMIT, Et as s3BucketArn, F as gray, Fn as AssemblyReader, Ft as withRetry, G as IAMRoleProvider, Gt as buildAssetRedirectMap, H as isExportAliasCollision, Hn as setAwsClients, Ht as AssetPublisher, I as green, In as processStackMessages, It as DagBuilder, J as ProviderRegistry, Jn as LocalInvokeBuildError$1, Jt as rewriteTemplateAssetReferences, K as collectInlinePolicyNamesManagedBySiblings, Kt as createAssetRedirectResolver, L as red, Lt as TemplateParser, M as formatResourceLine, Mn as PARTITION_TABLE, Mt as DiffCalculator, N as bold, Nn as canonicalizeRegion, Nt as INTRINSIC_KEYS, O as extractDeploymentEventError, On as MIGRATE_TMP_PREFIX, Ot as s3BucketDualStackDomainName, P as cyan, Pn as derivePartitionAndUrlSuffix, Pt as describeTypeWithThrottleRetry, Q as slowCcOperationTimeoutMs, Qn as MissingCdkCliError, Qt as BOOTSTRAP_MARKER_PREFIX, R as yellow, Rn as resolveBucketRegion, Rt as LockManager, S as ccRoutedFinalSnapshotError, Sn as resolveStateBucketWithDefaultAndSource, St as isSingleDynamicReferenceToken, T as refusesFinalSnapshot, Tn as warnDeprecatedNoPrefixCliFlag, Tt as scrubResourceRecord, U as secretBearingStateKeyWarning, Ut as stringifyValue, V as exportAliasCollisionScrubWarning, Vn as resetAwsClients, Vt as shouldRetainResource, W as stateKeySecretExposure, Wn as CdkdError, Wt as WorkGraph, X as findSilentDropProperties, Xn as LocalStartServiceError, Xt as stripControlChars, Y as findActionableSilentDrops, Yn as LocalMigrateError, Yt as escapeRegExp, Z as CloudControlProvider, Zt as AssetModeResolver, _ as IMPLICIT_DELETE_DEPENDENCIES, _n as resolveApp, _t as STATE_SOURCED_CROSS_GENERATION_RULES, a as DeploymentEventsStore, an as buildDenyExternalAccessPolicy, ar as StackTerminationProtectionError, at as WAFv2WebACLProvider, b as PRE_DELETE_SNAPSHOT_TYPES, bn as resolveSkipPrefix, bt as createSecretMasker, c as producerRegionsFromState, cn as getDockerCmd, ct as assertRegionMatch, d as updatePartialMessage, dn as AssetManifestLoader, dr as withErrorHandling, dt as configStringRefusal, en as getBootstrapMarkerKey, er as PartialFailureError, et as isTerminationProtectionPropagationError, f as updatePartialReason, fn as getDockerImageBySourceHash, fr as isMarkedNonRetryable, ft as readConfigString, g as maskingRetryLogger, gn as getLegacyStateBucketName, gt as requireConfigString, h as withResourceDeadline, hn as getDefaultStateBucketName, hr as markNonRetryable, ht as requireConfigObject, i as DeploymentEventsReader, in as validateContainerRepoName, ir as StackHasActiveImportsError, it as refStateLookupFromResource, j as renderStatefulReason, jn as expectedOwnerParam, jt as applyRoleArnIfSet, k as MULTI_REGION_RECREATE_BLOCKED_TYPES, kn as findLargeInlineResources, kt as s3BucketRegionalDomainName, l as replayFailedOperations, ln as runDockerForeground, lt as coerceCfnBoolean, m as deleteSkipReason, mn as synthesisStatusMessage, mr as isThrottlingError, mt as requireConfigArray, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as readBootstrapMarkerBody, nr as ResourceTimeoutError, nt as cfnRefValueFromPhysicalId, o as planFailedOps, on as buildDockerImage, ot as normalizeAwsTagsToCfn, p as UNSPECIFIED_SKIP_REASON, pn as Synthesizer, pr as isRetryableTransientError, pt as replayWarn, q as clearOnUpdateRemoval, qn as DeployCancelledError, qt as loadPublishableAssetManifest, r as DeployEngine, rn as validateAssetBucketName, rr as ResourceUpdateNotSupportedError, rt as getAccountInfo, s as planRollback, sn as formatDockerLoginError, st as resolveExplicitPhysicalId, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as parseBootstrapMarker, tr as ProvisioningError, tt as IntrinsicFunctionResolver, u as replayRollback, un as runDockerStreaming, ur as normalizeAwsError, ut as configBooleanRefusal, v as computeImplicitDeleteEdges, vn as resolveAutoAssetStorage, vt as STATE_SOURCED_READBACK_RULES, w as isFinalSnapshotError, wn as stateBucketExistenceConfirmed, wt as redactSecretsForState, x as buildFinalSnapshotIdentifier, xn as resolveStateBucketWithDefault, xt as dynamicReferenceTokens, y as ATOMIC_FINAL_SNAPSHOT_TYPES, yn as resolveCaptureObservedState, yt as TEMPLATE_SOURCED_RULES, z as collectDeclaredOutputNames, zn as AwsClients, zt as S3StateBackend } from "./deploy-engine-HfFU96oJ.js";
2
+ import { $ as isInterruptedWaitError, $n as LocalMigrateError, $t as escapeRegExp, A as isStatefulRecreateTargetSync, An as CFN_TEMPLATE_BODY_LIMIT, At as s3BucketArn, B as collectPublishedOutputNames, Bn as processStackMessages, Bt as DagBuilder, C as createPreDeleteFinalSnapshot, Cn as resolveCaptureObservedState, Ct as TEMPLATE_SOURCED_RULES, D as makeCanonicalizePropertiesFn, Dn as resolveUseCdkBootstrapAssets, Dt as maskSecretsInText, E as unsupportedFinalSnapshotError, En as resolveStateBucketWithDefaultAndSource, Et as isSingleDynamicReferenceToken, F as gray, Fn as expectedOwnerParam, Ft as applyRoleArnIfSet, G as IAMRoleProvider, Gn as resetAwsClients, Gt as shouldRetainResource, H as isExportAliasCollision, Hn as resolveBucketRegion, Ht as LockManager, I as green, In as PARTITION_TABLE, It as DiffCalculator, J as ProviderRegistry, Jn as CdkdError, Jt as WorkGraph, K as collectInlinePolicyNamesManagedBySiblings, Kn as setAwsClients, Kt as AssetPublisher, L as red, Ln as canonicalizeRegion, Lt as INTRINSIC_KEYS, M as formatResourceLine, Mn as MIGRATE_TMP_PREFIX, Mt as s3BucketDualStackDomainName, N as bold, Nn as findLargeInlineResources, Nt as s3BucketRegionalDomainName, O as extractDeploymentEventError, On as stateBucketExistenceConfirmed, Ot as redactSecretsForState, P as cyan, Pn as uploadCfnTemplate, Pt as s3BucketWebsiteUrl, Q as endCommandInterruptScope, Qn as LocalInvokeBuildError$1, Qt as rewriteTemplateAssetReferences, R as yellow, Rn as derivePartitionAndUrlSuffix, Rt as describeTypeWithThrottleRetry, S as ccRoutedFinalSnapshotError, Sn as resolveAutoAssetStorage, St as STATE_SOURCED_READBACK_RULES, T as refusesFinalSnapshot, Tn as resolveStateBucketWithDefault, Tt as dynamicReferenceTokens, U as secretBearingStateKeyWarning, Un as AwsClients, Ut as S3StateBackend, V as exportAliasCollisionScrubWarning, Vt as TemplateParser, W as stateKeySecretExposure, Wn as getAwsClients, Wt as rebuildClientForBucketRegion, X as findSilentDropProperties, Xt as createAssetRedirectResolver, Y as findActionableSilentDrops, Yt as buildAssetRedirectMap, Z as beginCommandInterruptScope, Zn as DeployCancelledError, Zt as loadPublishableAssetManifest, _ as IMPLICIT_DELETE_DEPENDENCIES, _n as Synthesizer, _r as isRetryableTransientError, _t as replayWarn, a as DeploymentEventsStore, an as parseBootstrapMarker, ar as ProvisioningError, at as IntrinsicFunctionResolver, b as PRE_DELETE_SNAPSHOT_TYPES, bn as getLegacyStateBucketName, bt as requireConfigString, c as producerRegionsFromState, cn as validateContainerRepoName, cr as StackHasActiveImportsError, ct as refStateLookupFromResource, d as updatePartialMessage, dn as formatDockerLoginError, dt as resolveExplicitPhysicalId, en as stripControlChars, er as LocalStartServiceError, et as startInterruptWatch, f as updatePartialReason, fn as getDockerCmd, ft as assertRegionMatch, g as maskingRetryLogger, gn as getDockerImageBySourceHash, gr as isMarkedNonRetryable, gt as readConfigString, h as withResourceDeadline, hn as AssetManifestLoader, hr as withErrorHandling, ht as configStringRefusal, i as DeploymentEventsReader, in as getBootstrapMarkerKey, ir as PartialFailureError, it as isTerminationProtectionPropagationError, j as renderStatefulReason, jn as CFN_TEMPLATE_URL_LIMIT, jt as s3BucketDomainName, k as MULTI_REGION_RECREATE_BLOCKED_TYPES, kn as warnDeprecatedNoPrefixCliFlag, kt as scrubResourceRecord, l as replayFailedOperations, ln as buildDenyExternalAccessPolicy, lr as StackTerminationProtectionError, lt as WAFv2WebACLProvider, m as deleteSkipReason, mn as runDockerStreaming, mr as normalizeAwsError, mt as configBooleanRefusal, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as BOOTSTRAP_MARKER_PREFIX, nr as MissingCdkCliError, nt as slowCcOperationTimeoutMs, o as planFailedOps, on as readBootstrapMarkerBody, or as ResourceTimeoutError, ot as cfnRefValueFromPhysicalId, p as UNSPECIFIED_SKIP_REASON, pn as runDockerForeground, pt as coerceCfnBoolean, q as clearOnUpdateRemoval, qt as stringifyValue, r as DeployEngine, rn as ensureAssetStorage, rr as NestedStackChildDirectDestroyError, rt as disableInstanceApiTermination, s as planRollback, sn as validateAssetBucketName, sr as ResourceUpdateNotSupportedError, st as getAccountInfo, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as AssetModeResolver, tt as CloudControlProvider, u as replayRollback, un as buildDockerImage, ut as normalizeAwsTagsToCfn, v as computeImplicitDeleteEdges, vn as synthesisStatusMessage, vr as isThrottlingError, vt as requireConfigArray, w as isFinalSnapshotError, wn as resolveSkipPrefix, wt as createSecretMasker, x as buildFinalSnapshotIdentifier, xn as resolveApp, xt as STATE_SOURCED_CROSS_GENERATION_RULES, y as ATOMIC_FINAL_SNAPSHOT_TYPES, yn as getDefaultStateBucketName, yr as markNonRetryable, yt as requireConfigObject, z as collectDeclaredOutputNames, zn as AssemblyReader, zt as withRetry } from "./deploy-engine-BpZWPiaL.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as getCurrentStackName, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
4
- import { t as ASGProvider } from "./asg-provider-Cucl2K22.js";
4
+ import { t as ASGProvider } from "./asg-provider-B2lPfJBN.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -17,7 +17,7 @@ import { APIGatewayClient, CreateAuthorizerCommand, CreateDeploymentCommand, Cre
17
17
  import { CreateEventBusCommand, DeleteEventBusCommand, DeleteRuleCommand, DescribeEventBusCommand, DescribeRuleCommand, EventBridgeClient, ListRulesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$1, ListTargetsByRuleCommand, PutRuleCommand, PutTargetsCommand, RemoveTargetsCommand, ResourceNotFoundException as ResourceNotFoundException$2, TagResourceCommand as TagResourceCommand$4, UntagResourceCommand as UntagResourceCommand$4, UpdateEventBusCommand } from "@aws-sdk/client-eventbridge";
18
18
  import { CreateSecretCommand, DeleteSecretCommand, DescribeSecretCommand, GetSecretValueCommand, RemoveRegionsFromReplicationCommand, ReplicateSecretToRegionsCommand, ResourceNotFoundException as ResourceNotFoundException$3, SecretsManagerClient, TagResourceCommand as TagResourceCommand$5, UntagResourceCommand as UntagResourceCommand$5, UpdateSecretCommand } from "@aws-sdk/client-secrets-manager";
19
19
  import { AddTagsToResourceCommand, DeleteParameterCommand, DescribeParametersCommand, GetParameterCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$2, ParameterNotFound, PutParameterCommand, RemoveTagsFromResourceCommand, SSMClient } from "@aws-sdk/client-ssm";
20
- import { CloudFrontClient, CreateCloudFrontOriginAccessIdentityCommand, CreateDistributionCommand, CreateDistributionWithTagsCommand, CreateOriginAccessControlCommand, DeleteCloudFrontOriginAccessIdentityCommand, DeleteDistributionCommand, DeleteOriginAccessControlCommand, GetCloudFrontOriginAccessIdentityCommand, GetDistributionCommand, GetDistributionConfigCommand, GetOriginAccessControlCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$3, NoSuchCloudFrontOriginAccessIdentity, NoSuchDistribution, NoSuchOriginAccessControl, TagResourceCommand as TagResourceCommand$6, UntagResourceCommand as UntagResourceCommand$6, UpdateCloudFrontOriginAccessIdentityCommand, UpdateDistributionCommand, UpdateOriginAccessControlCommand } from "@aws-sdk/client-cloudfront";
20
+ import { CloudFrontClient, CreateCloudFrontOriginAccessIdentityCommand, CreateDistributionCommand, CreateDistributionWithTagsCommand, CreateOriginAccessControlCommand, DeleteCloudFrontOriginAccessIdentityCommand, DeleteDistributionCommand, DeleteOriginAccessControlCommand, DistributionAlreadyExists, GetCloudFrontOriginAccessIdentityCommand, GetDistributionCommand, GetDistributionConfigCommand, GetOriginAccessControlCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$3, NoSuchCloudFrontOriginAccessIdentity, NoSuchDistribution, NoSuchOriginAccessControl, TagResourceCommand as TagResourceCommand$6, UntagResourceCommand as UntagResourceCommand$6, UpdateCloudFrontOriginAccessIdentityCommand, UpdateDistributionCommand, UpdateOriginAccessControlCommand } from "@aws-sdk/client-cloudfront";
21
21
  import { CloudWatchClient, DeleteAlarmsCommand, DeleteAnomalyDetectorCommand, DescribeAlarmsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$4, PutAnomalyDetectorCommand, PutMetricAlarmCommand, ResourceNotFoundException as ResourceNotFoundException$4, TagResourceCommand as TagResourceCommand$7, UntagResourceCommand as UntagResourceCommand$7 } from "@aws-sdk/client-cloudwatch";
22
22
  import { AssociateKmsKeyCommand, CloudWatchLogsClient, CreateLogGroupCommand, DeleteDataProtectionPolicyCommand, DeleteIndexPolicyCommand, DeleteLogGroupCommand, DeleteRetentionPolicyCommand, DescribeIndexPoliciesCommand, DescribeLogGroupsCommand, DisassociateKmsKeyCommand, GetDataProtectionPolicyCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$5, PutBearerTokenAuthenticationCommand, PutDataProtectionPolicyCommand, PutIndexPolicyCommand, PutLogGroupDeletionProtectionCommand, PutRetentionPolicyCommand, ResourceAlreadyExistsException, ResourceNotFoundException as ResourceNotFoundException$5, TagResourceCommand as TagResourceCommand$8, UntagResourceCommand as UntagResourceCommand$8 } from "@aws-sdk/client-cloudwatch-logs";
23
23
  import { BedrockAgentCoreControlClient, CreateAgentRuntimeCommand, CreateEvaluatorCommand, DeleteAgentRuntimeCommand, DeleteEvaluatorCommand, GetAgentRuntimeCommand, GetBrowserCommand, GetCodeInterpreterCommand, GetEvaluatorCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$6, ResourceNotFoundException as ResourceNotFoundException$6, TagResourceCommand as TagResourceCommand$9, UntagResourceCommand as UntagResourceCommand$9, UpdateAgentRuntimeCommand, UpdateEvaluatorCommand } from "@aws-sdk/client-bedrock-agentcore-control";
@@ -1774,6 +1774,7 @@ function createListCommand() {
1774
1774
  * handler of its own).
1775
1775
  */
1776
1776
  function forwardSigtermToSigint() {
1777
+ beginCommandInterruptScope();
1777
1778
  const handler = () => {
1778
1779
  if (process.listenerCount("SIGINT") === 0) process.exit(143);
1779
1780
  process.emit("SIGINT", "SIGINT");
@@ -1781,6 +1782,7 @@ function forwardSigtermToSigint() {
1781
1782
  process.on("SIGTERM", handler);
1782
1783
  return () => {
1783
1784
  process.removeListener("SIGTERM", handler);
1785
+ endCommandInterruptScope();
1784
1786
  };
1785
1787
  }
1786
1788
 
@@ -10863,6 +10865,23 @@ function mapDeliveryStatusLogging(attrs, stateCaseMap = /* @__PURE__ */ new Map(
10863
10865
  //#endregion
10864
10866
  //#region src/provisioning/providers/sns-subscription-provider.ts
10865
10867
  /**
10868
+ * The "old subscription was not deleted" reason the THROWN-delete arm of
10869
+ * `update()` reports (issue
10870
+ * [#1967](https://github.com/go-to-k/cdkd/issues/1967)).
10871
+ *
10872
+ * A constant, and FIXED WORDING with no interpolation, for the reason the
10873
+ * abort block spells out at length: this string is rendered onto the warn line
10874
+ * beside a message that must stay clear of the substrings
10875
+ * `retryable-errors.ts` classifies on, and the underlying AWS error text — an
10876
+ * `Unsubscribe` throttle, a `does not exist` — is user-uncontrolled and
10877
+ * carries several of them. The AWS message goes out on its own warn line in
10878
+ * the `catch`, where nothing classifies it.
10879
+ *
10880
+ * Its skip-arm twin is the provider-supplied `ResourceDeleteResult.reason`,
10881
+ * which is subject to the same rule at its own producer.
10882
+ */
10883
+ const SNS_SUBSCRIPTION_DELETE_THREW_REASON = "the Unsubscribe call did not complete, so the old subscription is unproven";
10884
+ /**
10866
10885
  * AWS SNS Subscription Provider
10867
10886
  *
10868
10887
  * Implements resource provisioning for AWS::SNS::Subscription using the SNS SDK.
@@ -10941,14 +10960,17 @@ var SNSSubscriptionProvider = class {
10941
10960
  async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
10942
10961
  this.logger.debug(`Updating SNS subscription ${logicalId}: ${physicalId}`);
10943
10962
  let deleteResult = void 0;
10963
+ let deleteThrew = false;
10944
10964
  try {
10945
10965
  deleteResult = await this.delete(logicalId, physicalId, resourceType);
10946
10966
  } catch (error) {
10967
+ deleteThrew = true;
10947
10968
  this.logger.warn(`Failed to delete old subscription ${physicalId} during update: ${String(error)}`);
10948
10969
  }
10949
- if (deleteResult?.outcome === "skipped") {
10950
- this.logger.warn(`Cannot replace SNS subscription ${logicalId}: the old subscription ${physicalId} was not deleted — ${deleteResult.reason}`);
10951
- throw markNonRetryable(new ProvisioningError(`Cannot replace SNS subscription ${logicalId}: cdkd could not delete the old subscription, and creating the replacement would leave both subscribed to the topic and deliver every message twice. See the warning naming ${logicalId} for the recorded physical id and the cause. Repair the state record for ${logicalId}, or remove the old subscription in AWS if it is still live, then re-run.`, resourceType, logicalId, physicalId));
10970
+ const notDeletedReason = deleteThrew ? SNS_SUBSCRIPTION_DELETE_THREW_REASON : deleteResult?.outcome === "skipped" ? deleteResult.reason : void 0;
10971
+ if (notDeletedReason !== void 0) {
10972
+ this.logger.warn(`Cannot replace SNS subscription ${logicalId}: the old subscription ${physicalId} was not deleted ${notDeletedReason}`);
10973
+ throw markNonRetryable(new ProvisioningError(`Cannot replace SNS subscription ${logicalId}: cdkd could not delete the old subscription, and creating the replacement would leave both subscribed to the topic and deliver every message twice. See the warning naming ${logicalId} for the recorded physical id and the cause. Usually the Unsubscribe itself failed (a throttle, a denied sns:Unsubscribe) and the state record is FINE — clear that cause and re-run, or remove the old subscription in AWS by hand. Repair the state record only when the warning says the recorded physical id is malformed, which is the latent skip arm rather than this one.`, resourceType, logicalId, physicalId));
10952
10974
  }
10953
10975
  const createResult = await this.create(logicalId, resourceType, properties);
10954
10976
  return {
@@ -14819,28 +14841,37 @@ const DELETE_INDEX_WAIT_PROCEED_NOTE = `the DeleteTable goes ahead anyway and AW
14819
14841
  async function waitForIndexesSettled(opts) {
14820
14842
  const { tableName, logicalId, logger, describeTable, maxAttempts, proceedNote } = opts;
14821
14843
  const budgetWait = opts.budgetWait;
14844
+ const watch = startInterruptWatch(`index settle wait on ${tableName}`);
14822
14845
  let attempt = 0;
14823
- while (budgetWait ? budgetWait.nextPoll() : attempt < maxAttempts) {
14824
- attempt += 1;
14825
- try {
14826
- if (!hasTransitionalIndex((await describeTable()).Table?.GlobalSecondaryIndexes ?? [])) return;
14827
- } catch (err) {
14828
- const message = err instanceof Error ? err.message : String(err);
14829
- if (isThrottlingError(err)) {
14830
- logger.debug(`DescribeTable throttled while waiting for indexes on ${tableName} (attempt ${attempt}/${maxAttempts}); still waiting: ${message}`);
14831
- await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
14832
- continue;
14846
+ try {
14847
+ while (budgetWait ? budgetWait.nextPoll() : attempt < maxAttempts) {
14848
+ attempt += 1;
14849
+ if (watch.isInterrupted()) {
14850
+ logger.debug(`Interrupted while waiting for indexes on ${tableName} (${logicalId}) after ${attempt - 1} DescribeTable polls; ${proceedNote}`);
14851
+ return;
14833
14852
  }
14834
- if (err instanceof ResourceNotFoundException$1) {
14835
- logger.debug(`Table ${tableName} (${logicalId}) no longer exists while waiting for indexes`);
14853
+ try {
14854
+ if (!hasTransitionalIndex((await describeTable()).Table?.GlobalSecondaryIndexes ?? [])) return;
14855
+ } catch (err) {
14856
+ const message = err instanceof Error ? err.message : String(err);
14857
+ if (isThrottlingError(err)) {
14858
+ logger.debug(`DescribeTable throttled while waiting for indexes on ${tableName} (attempt ${attempt}/${maxAttempts}); still waiting: ${message}`);
14859
+ await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
14860
+ continue;
14861
+ }
14862
+ if (err instanceof ResourceNotFoundException$1) {
14863
+ logger.debug(`Table ${tableName} (${logicalId}) no longer exists while waiting for indexes`);
14864
+ return;
14865
+ }
14866
+ const errorName = err instanceof Error ? err.name : typeof err;
14867
+ logger.debug(`DescribeTable failed while waiting for indexes on ${tableName} (${logicalId}): ${message}`);
14868
+ logger.warn(`DescribeTable failed (${errorName}) while waiting for indexes on ${tableName} (${logicalId}), so cdkd stopped waiting; ${proceedNote} Re-run with --verbose for AWS's own message.`);
14836
14869
  return;
14837
14870
  }
14838
- const errorName = err instanceof Error ? err.name : typeof err;
14839
- logger.debug(`DescribeTable failed while waiting for indexes on ${tableName} (${logicalId}): ${message}`);
14840
- logger.warn(`DescribeTable failed (${errorName}) while waiting for indexes on ${tableName} (${logicalId}), so cdkd stopped waiting; ${proceedNote} Re-run with --verbose for AWS's own message.`);
14841
- return;
14871
+ await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
14842
14872
  }
14843
- await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
14873
+ } finally {
14874
+ watch.dispose();
14844
14875
  }
14845
14876
  const pollSeconds = INDEX_SETTLE_POLL_INTERVAL_MS / 1e3;
14846
14877
  const pollsRun = budgetWait ? budgetWait.pollsRun : maxAttempts;
@@ -14945,24 +14976,31 @@ function indexBusyRetryWarning(opts) {
14945
14976
  */
14946
14977
  async function deleteTableWithIndexBusyRetry(opts) {
14947
14978
  let deleteAttempts = 0;
14948
- await withRetry(async () => {
14949
- if (deleteAttempts++ > 0) {
14950
- if (deleteAttempts === 2) opts.logger.warn(indexBusyRetryWarning({
14951
- typeLabel: opts.typeLabel,
14952
- logicalId: opts.logicalId,
14953
- physicalId: opts.physicalId,
14954
- attemptNumber: deleteAttempts,
14955
- maxRetries: opts.maxRetries
14956
- }));
14957
- await opts.reArm();
14958
- }
14959
- await opts.deleteTable();
14960
- }, opts.logicalId, {
14961
- maxRetries: opts.maxRetries,
14962
- isRetryable: (message) => isIndexBusyDeleteError(message) && (opts.shouldKeepRetrying?.() ?? true),
14963
- logger: opts.logger,
14964
- sleep: (ms) => opts.sleepSeam.sleep ? opts.sleepSeam.sleep(ms) : new Promise((resolve) => setTimeout(resolve, ms))
14965
- });
14979
+ const watch = startInterruptWatch(`DynamoDB ${opts.typeLabel} ${opts.logicalId} delete`);
14980
+ try {
14981
+ await withRetry(async () => {
14982
+ if (deleteAttempts++ > 0) {
14983
+ if (deleteAttempts === 2) opts.logger.warn(indexBusyRetryWarning({
14984
+ typeLabel: opts.typeLabel,
14985
+ logicalId: opts.logicalId,
14986
+ physicalId: opts.physicalId,
14987
+ attemptNumber: deleteAttempts,
14988
+ maxRetries: opts.maxRetries
14989
+ }));
14990
+ await opts.reArm();
14991
+ }
14992
+ await opts.deleteTable();
14993
+ }, opts.logicalId, {
14994
+ maxRetries: opts.maxRetries,
14995
+ isRetryable: (message) => isIndexBusyDeleteError(message) && (opts.shouldKeepRetrying?.() ?? true),
14996
+ logger: opts.logger,
14997
+ isInterrupted: watch.isInterrupted,
14998
+ onInterrupted: watch.onInterrupted,
14999
+ sleep: (ms) => opts.sleepSeam.sleep ? opts.sleepSeam.sleep(ms) : new Promise((resolve) => setTimeout(resolve, ms))
15000
+ });
15001
+ } finally {
15002
+ watch.dispose();
15003
+ }
14966
15004
  }
14967
15005
 
14968
15006
  //#endregion
@@ -18638,94 +18676,111 @@ var DynamoDBGlobalTableProvider = class {
18638
18676
  const oldEnabled = oldSettings !== void 0 && oldSettings !== null;
18639
18677
  const newEnabled = newSettings !== void 0 && newSettings !== null;
18640
18678
  if (!oldEnabled && !newEnabled) return;
18641
- if (newEnabled) {
18642
- const minCapacity = Number(newSettings["MinCapacity"] ?? 0);
18643
- const maxCapacity = Number(newSettings["MaxCapacity"] ?? 0);
18644
- if (!Number.isFinite(minCapacity) || !Number.isFinite(maxCapacity)) {
18645
- warn(`Cannot apply auto-scaling diff on ${tableName} (${dimension}): MinCapacity / MaxCapacity must be numbers, got ${maskSecrets(String(newSettings["MinCapacity"]))} / ${maskSecrets(String(newSettings["MaxCapacity"]))}`);
18679
+ const watch = startInterruptWatch(`DynamoDB auto-scaling for ${tableName}`);
18680
+ try {
18681
+ if (newEnabled) {
18682
+ const minCapacity = Number(newSettings["MinCapacity"] ?? 0);
18683
+ const maxCapacity = Number(newSettings["MaxCapacity"] ?? 0);
18684
+ if (!Number.isFinite(minCapacity) || !Number.isFinite(maxCapacity)) {
18685
+ warn(`Cannot apply auto-scaling diff on ${tableName} (${dimension}): MinCapacity / MaxCapacity must be numbers, got ${maskSecrets(String(newSettings["MinCapacity"]))} / ${maskSecrets(String(newSettings["MaxCapacity"]))}`);
18686
+ return;
18687
+ }
18688
+ try {
18689
+ await withRetry(() => asClient.send(new RegisterScalableTargetCommand({
18690
+ ServiceNamespace: "dynamodb",
18691
+ ResourceId: resourceId,
18692
+ ScalableDimension: dimension,
18693
+ MinCapacity: minCapacity,
18694
+ MaxCapacity: maxCapacity
18695
+ })), `${resourceId} (${dimension})`, {
18696
+ isRetryable: (_message, error) => isThrottlingError(error),
18697
+ maxRetries: asMaxRetries,
18698
+ isInterrupted: watch.isInterrupted,
18699
+ onInterrupted: watch.onInterrupted,
18700
+ ...autoScalingRetryDelays.sleep ? { sleep: autoScalingRetryDelays.sleep } : {}
18701
+ });
18702
+ } catch (err) {
18703
+ if (isInterruptedWaitError(err)) throw err;
18704
+ warn(`Could not register auto-scaling target on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling register-scalable-target --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension} --min-capacity ${minCapacity} --max-capacity ${maxCapacity}`);
18705
+ return;
18706
+ }
18707
+ const tttCfg = newSettings["TargetTrackingScalingPolicyConfiguration"] ?? {};
18708
+ const targetValue = Number(tttCfg["TargetValue"]);
18709
+ if (!Number.isFinite(targetValue)) {
18710
+ warn(`Auto-scaling target registered on ${tableName} (${dimension}) but TargetValue is missing or non-numeric — skipping PutScalingPolicy. Provide TargetTrackingScalingPolicyConfiguration.TargetValue in the template.`);
18711
+ return;
18712
+ }
18713
+ const targetTrackingConfig = {
18714
+ PredefinedMetricSpecification: { PredefinedMetricType: metricType },
18715
+ TargetValue: targetValue
18716
+ };
18717
+ if (tttCfg["ScaleInCooldown"] !== void 0) targetTrackingConfig["ScaleInCooldown"] = Number(tttCfg["ScaleInCooldown"]);
18718
+ if (tttCfg["ScaleOutCooldown"] !== void 0) targetTrackingConfig["ScaleOutCooldown"] = Number(tttCfg["ScaleOutCooldown"]);
18719
+ if (tttCfg["DisableScaleIn"] !== void 0) targetTrackingConfig["DisableScaleIn"] = Boolean(tttCfg["DisableScaleIn"]);
18720
+ try {
18721
+ await withRetry(() => asClient.send(new PutScalingPolicyCommand({
18722
+ PolicyName: policyName,
18723
+ ServiceNamespace: "dynamodb",
18724
+ ResourceId: resourceId,
18725
+ ScalableDimension: dimension,
18726
+ PolicyType: "TargetTrackingScaling",
18727
+ TargetTrackingScalingPolicyConfiguration: targetTrackingConfig
18728
+ })), `${policyName}`, {
18729
+ isRetryable: (_message, error) => isThrottlingError(error),
18730
+ maxRetries: asMaxRetries,
18731
+ isInterrupted: watch.isInterrupted,
18732
+ onInterrupted: watch.onInterrupted,
18733
+ ...autoScalingRetryDelays.sleep ? { sleep: autoScalingRetryDelays.sleep } : {}
18734
+ });
18735
+ this.logger.debug(`Upserted auto-scaling policy ${policyName} on ${tableName} (${dimension})`);
18736
+ } catch (err) {
18737
+ if (isInterruptedWaitError(err)) throw err;
18738
+ warn(`Could not put auto-scaling policy on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling put-scaling-policy --policy-name ${policyName} --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension} --policy-type TargetTrackingScaling`);
18739
+ }
18646
18740
  return;
18647
18741
  }
18742
+ const isObjectNotFound = (err) => {
18743
+ if (!(err instanceof Error)) return false;
18744
+ const name = err.name ?? "";
18745
+ const msg = err.message ?? "";
18746
+ return name === "ObjectNotFoundException" || msg.includes("No scaling policy found") || msg.includes("No scalable target found");
18747
+ };
18648
18748
  try {
18649
- await withRetry(() => asClient.send(new RegisterScalableTargetCommand({
18749
+ await withRetry(() => asClient.send(new DeleteScalingPolicyCommand({
18750
+ PolicyName: policyName,
18650
18751
  ServiceNamespace: "dynamodb",
18651
18752
  ResourceId: resourceId,
18652
- ScalableDimension: dimension,
18653
- MinCapacity: minCapacity,
18654
- MaxCapacity: maxCapacity
18753
+ ScalableDimension: dimension
18655
18754
  })), `${resourceId} (${dimension})`, {
18656
18755
  isRetryable: (_message, error) => isThrottlingError(error),
18657
18756
  maxRetries: asMaxRetries,
18757
+ isInterrupted: watch.isInterrupted,
18758
+ onInterrupted: watch.onInterrupted,
18658
18759
  ...autoScalingRetryDelays.sleep ? { sleep: autoScalingRetryDelays.sleep } : {}
18659
18760
  });
18660
18761
  } catch (err) {
18661
- warn(`Could not register auto-scaling target on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling register-scalable-target --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension} --min-capacity ${minCapacity} --max-capacity ${maxCapacity}`);
18662
- return;
18762
+ if (isInterruptedWaitError(err)) throw err;
18763
+ if (!isObjectNotFound(err)) warn(`Could not delete auto-scaling policy on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling delete-scaling-policy --policy-name ${policyName} --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension}`);
18663
18764
  }
18664
- const tttCfg = newSettings["TargetTrackingScalingPolicyConfiguration"] ?? {};
18665
- const targetValue = Number(tttCfg["TargetValue"]);
18666
- if (!Number.isFinite(targetValue)) {
18667
- warn(`Auto-scaling target registered on ${tableName} (${dimension}) but TargetValue is missing or non-numeric — skipping PutScalingPolicy. Provide TargetTrackingScalingPolicyConfiguration.TargetValue in the template.`);
18668
- return;
18669
- }
18670
- const targetTrackingConfig = {
18671
- PredefinedMetricSpecification: { PredefinedMetricType: metricType },
18672
- TargetValue: targetValue
18673
- };
18674
- if (tttCfg["ScaleInCooldown"] !== void 0) targetTrackingConfig["ScaleInCooldown"] = Number(tttCfg["ScaleInCooldown"]);
18675
- if (tttCfg["ScaleOutCooldown"] !== void 0) targetTrackingConfig["ScaleOutCooldown"] = Number(tttCfg["ScaleOutCooldown"]);
18676
- if (tttCfg["DisableScaleIn"] !== void 0) targetTrackingConfig["DisableScaleIn"] = Boolean(tttCfg["DisableScaleIn"]);
18677
18765
  try {
18678
- await withRetry(() => asClient.send(new PutScalingPolicyCommand({
18679
- PolicyName: policyName,
18766
+ await withRetry(() => asClient.send(new DeregisterScalableTargetCommand({
18680
18767
  ServiceNamespace: "dynamodb",
18681
18768
  ResourceId: resourceId,
18682
- ScalableDimension: dimension,
18683
- PolicyType: "TargetTrackingScaling",
18684
- TargetTrackingScalingPolicyConfiguration: targetTrackingConfig
18685
- })), `${policyName}`, {
18769
+ ScalableDimension: dimension
18770
+ })), `${resourceId} (${dimension})`, {
18686
18771
  isRetryable: (_message, error) => isThrottlingError(error),
18687
18772
  maxRetries: asMaxRetries,
18773
+ isInterrupted: watch.isInterrupted,
18774
+ onInterrupted: watch.onInterrupted,
18688
18775
  ...autoScalingRetryDelays.sleep ? { sleep: autoScalingRetryDelays.sleep } : {}
18689
18776
  });
18690
- this.logger.debug(`Upserted auto-scaling policy ${policyName} on ${tableName} (${dimension})`);
18777
+ this.logger.debug(`Deregistered auto-scaling target ${resourceId} (${dimension})`);
18691
18778
  } catch (err) {
18692
- warn(`Could not put auto-scaling policy on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling put-scaling-policy --policy-name ${policyName} --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension} --policy-type TargetTrackingScaling`);
18779
+ if (isInterruptedWaitError(err)) throw err;
18780
+ if (!isObjectNotFound(err)) warn(`Could not deregister auto-scaling target on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling deregister-scalable-target --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension}`);
18693
18781
  }
18694
- return;
18695
- }
18696
- const isObjectNotFound = (err) => {
18697
- if (!(err instanceof Error)) return false;
18698
- const name = err.name ?? "";
18699
- const msg = err.message ?? "";
18700
- return name === "ObjectNotFoundException" || msg.includes("No scaling policy found") || msg.includes("No scalable target found");
18701
- };
18702
- try {
18703
- await withRetry(() => asClient.send(new DeleteScalingPolicyCommand({
18704
- PolicyName: policyName,
18705
- ServiceNamespace: "dynamodb",
18706
- ResourceId: resourceId,
18707
- ScalableDimension: dimension
18708
- })), `${resourceId} (${dimension})`, {
18709
- isRetryable: (_message, error) => isThrottlingError(error),
18710
- maxRetries: asMaxRetries,
18711
- ...autoScalingRetryDelays.sleep ? { sleep: autoScalingRetryDelays.sleep } : {}
18712
- });
18713
- } catch (err) {
18714
- if (!isObjectNotFound(err)) warn(`Could not delete auto-scaling policy on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling delete-scaling-policy --policy-name ${policyName} --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension}`);
18715
- }
18716
- try {
18717
- await withRetry(() => asClient.send(new DeregisterScalableTargetCommand({
18718
- ServiceNamespace: "dynamodb",
18719
- ResourceId: resourceId,
18720
- ScalableDimension: dimension
18721
- })), `${resourceId} (${dimension})`, {
18722
- isRetryable: (_message, error) => isThrottlingError(error),
18723
- maxRetries: asMaxRetries,
18724
- ...autoScalingRetryDelays.sleep ? { sleep: autoScalingRetryDelays.sleep } : {}
18725
- });
18726
- this.logger.debug(`Deregistered auto-scaling target ${resourceId} (${dimension})`);
18727
- } catch (err) {
18728
- if (!isObjectNotFound(err)) warn(`Could not deregister auto-scaling target on ${tableName} (${dimension}): ${err instanceof Error ? err.message : String(err)}. Run: aws application-autoscaling deregister-scalable-target --service-namespace dynamodb --resource-id ${resourceId} --scalable-dimension ${dimension}`);
18782
+ } finally {
18783
+ watch.dispose();
18729
18784
  }
18730
18785
  }
18731
18786
  /**
@@ -18923,6 +18978,7 @@ var DynamoDBGlobalTableProvider = class {
18923
18978
  }
18924
18979
  } catch (describeErr) {
18925
18980
  if (describeErr instanceof ProvisioningError) throw describeErr;
18981
+ if (isInterruptedWaitError(describeErr)) throw describeErr;
18926
18982
  if (!(describeErr instanceof ResourceNotFoundException$1)) {
18927
18983
  const cause = describeErr instanceof Error ? describeErr : void 0;
18928
18984
  throw new ProvisioningError(`Failed to describe DynamoDB GlobalTable ${logicalId} before delete: ${describeErr instanceof Error ? describeErr.message : String(describeErr)}`, resourceType, logicalId, physicalId, cause);
@@ -19634,12 +19690,20 @@ var DynamoDBGlobalTableProvider = class {
19634
19690
  */
19635
19691
  async waitForReplicaGone(tableName, region, logicalId, maxAttempts = REPLICA_GONE_WAIT_ATTEMPTS, budget) {
19636
19692
  const wait = beginDeleteWait(maxAttempts, budget);
19637
- while (wait.nextPoll()) try {
19638
- if (!(await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.Replicas?.find((r) => r.RegionName === region)) return;
19639
- await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
19640
- } catch (err) {
19641
- if (err instanceof ResourceNotFoundException$1) return;
19642
- throw err;
19693
+ const watch = startInterruptWatch(`DynamoDB replica ${region} teardown on ${tableName}`);
19694
+ try {
19695
+ while (wait.nextPoll()) {
19696
+ if (watch.isInterrupted()) throw watch.onInterrupted();
19697
+ try {
19698
+ if (!(await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }))).Table?.Replicas?.find((r) => r.RegionName === region)) return;
19699
+ await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
19700
+ } catch (err) {
19701
+ if (err instanceof ResourceNotFoundException$1) return;
19702
+ throw err;
19703
+ }
19704
+ }
19705
+ } finally {
19706
+ watch.dispose();
19643
19707
  }
19644
19708
  throw new ProvisioningError(`Replica ${region} for table ${tableName} did not disappear within ${wait.pollsRun}s${wait.note()}`, "AWS::DynamoDB::GlobalTable", logicalId, tableName);
19645
19709
  }
@@ -19659,12 +19723,23 @@ var DynamoDBGlobalTableProvider = class {
19659
19723
  */
19660
19724
  async waitForTableGone(tableName, logicalId, maxAttempts = 600, budget) {
19661
19725
  const wait = beginDeleteWait(maxAttempts, budget);
19662
- while (wait.nextPoll()) try {
19663
- await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }));
19664
- await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
19665
- } catch (err) {
19666
- if (err instanceof ResourceNotFoundException$1) return;
19667
- throw err;
19726
+ const watch = startInterruptWatch(`DynamoDB table ${tableName} deletion wait`);
19727
+ try {
19728
+ while (wait.nextPoll()) {
19729
+ if (watch.isInterrupted()) {
19730
+ this.logger.warn(`DynamoDB GlobalTable ${logicalId}: interrupted while waiting for ${tableName} to disappear. AWS ACCEPTED the DeleteTable and will finish it; cdkd stopped watching.`);
19731
+ return;
19732
+ }
19733
+ try {
19734
+ await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: tableName }));
19735
+ await new Promise((resolve) => setTimeout(resolve, INDEX_SETTLE_POLL_INTERVAL_MS));
19736
+ } catch (err) {
19737
+ if (err instanceof ResourceNotFoundException$1) return;
19738
+ throw err;
19739
+ }
19740
+ }
19741
+ } finally {
19742
+ watch.dispose();
19668
19743
  }
19669
19744
  if (wait.cutShort) {
19670
19745
  this.logger.warn(`DynamoDB GlobalTable ${logicalId}: DeleteTable on ${tableName} was ACCEPTED by AWS but cdkd stopped waiting for the table to disappear${wait.note()} The delete completes on AWS's side; a later describe (or a re-run of this destroy) sees it gone.`);
@@ -30751,13 +30826,19 @@ var CloudFrontDistributionProvider = class {
30751
30826
  /**
30752
30827
  * Create a CloudFront Distribution
30753
30828
  */
30754
- async create(logicalId, resourceType, properties) {
30829
+ async create(logicalId, resourceType, properties, context) {
30755
30830
  this.logger.debug(`Creating CloudFront Distribution ${logicalId}`);
30831
+ const maskSecrets = context?.maskSecrets ?? ((text) => text);
30832
+ const callerReferenceToken = acquireIdempotencyToken({
30833
+ scope: "CreateDistribution",
30834
+ logicalId,
30835
+ maxLength: 128
30836
+ });
30756
30837
  try {
30757
30838
  const distributionConfig = properties["DistributionConfig"] ?? {};
30758
30839
  const sdkConfig = this.convertToSdkFormat({
30759
30840
  ...distributionConfig,
30760
- CallerReference: `${Date.now()}-${logicalId}-${Math.random().toString(36).slice(2, 8)}`
30841
+ CallerReference: callerReferenceToken.value
30761
30842
  });
30762
30843
  const sdkTags = this.toSdkTags(properties["Tags"]);
30763
30844
  const distribution = (sdkTags ? await this.cloudFrontClient.send(new CreateDistributionWithTagsCommand({ DistributionConfigWithTags: {
@@ -30768,6 +30849,7 @@ var CloudFrontDistributionProvider = class {
30768
30849
  const domainName = distribution.DomainName;
30769
30850
  this.logger.debug(`Created CloudFront Distribution: ${distributionId} (${domainName})`);
30770
30851
  await this.settleDistribution(logicalId, distributionId);
30852
+ callerReferenceToken.release();
30771
30853
  return {
30772
30854
  physicalId: distributionId,
30773
30855
  attributes: {
@@ -30777,11 +30859,51 @@ var CloudFrontDistributionProvider = class {
30777
30859
  }
30778
30860
  };
30779
30861
  } catch (error) {
30862
+ if (error instanceof DistributionAlreadyExists || error?.name === "DistributionAlreadyExists") {
30863
+ const cause = error instanceof Error ? error : void 0;
30864
+ throw markNonRetryable(new ProvisioningError(maskSecrets(`Failed to create CloudFront Distribution ${logicalId}: CloudFront refused the create because this deploy's CallerReference had been used before, which means an EARLIER attempt of this same create succeeded and cdkd never received its response. That distribution is LIVE, is not in cdkd state, and 'cdkd destroy' will not remove it — it bills until you delete it by hand. CloudFront does not return CallerReference from ListDistributions, so find it with 'aws cloudfront list-distributions' and match on ${this.orphanSearchHint(properties, maskSecrets)}; then disable and delete it (or import it with 'cdkd import') before re-running. The AWS error is on this error's cause.`), resourceType, logicalId, void 0, cause));
30865
+ }
30780
30866
  const cause = error instanceof Error ? error : void 0;
30781
30867
  throw new ProvisioningError(`Failed to create CloudFront Distribution ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
30782
30868
  }
30783
30869
  }
30784
30870
  /**
30871
+ * The identifying fields of the distribution that WOULD have been created,
30872
+ * for the `DistributionAlreadyExists` orphan message (issue #2079).
30873
+ *
30874
+ * Only fields `ListDistributions` actually returns are worth naming — the
30875
+ * user has to match the orphan in that output by eye. `Origins[].DomainName`
30876
+ * and `Comment` are both on `DistributionSummary`; `CallerReference` is not,
30877
+ * which is the whole reason this message exists.
30878
+ *
30879
+ * Read from the RAW template properties rather than the converted SDK config
30880
+ * so the helper stays usable from the catch, which is outside the scope
30881
+ * where `sdkConfig` exists. Both the bare-array (CFn) and `{ Quantity,
30882
+ * Items }` (SDK) spellings of `Origins` are accepted because a template may
30883
+ * carry either.
30884
+ *
30885
+ * **Every value it returns is a RESOLVED template value, so each is masked
30886
+ * HERE, at the leaf** (issue #1932 item 3, the shape
30887
+ * `apigatewayv2-provider.ts`'s `toSdkResponseParameters` established). Two
30888
+ * reasons the caller's outer mask cannot stand alone, both from
30889
+ * `.claude/rules/providers.md`: `maskSecretsInText`'s SUBSTRING arm ignores a
30890
+ * needle shorter than `MIN_NEEDLE_LENGTH` (4), and `JSON.stringify` escapes
30891
+ * quotes — so a Secrets Manager JSON document no longer OCCURS literally in
30892
+ * the finished sentence. Masking the raw string first reaches the
30893
+ * WHOLE-VALUE arm at any length, and the mask is idempotent, so both layers
30894
+ * are free.
30895
+ */
30896
+ orphanSearchHint(properties, maskSecrets) {
30897
+ const config = properties["DistributionConfig"] ?? {};
30898
+ const origins = config["Origins"];
30899
+ const domains = (Array.isArray(origins) ? origins : Array.isArray(origins?.Items) ? origins.Items ?? [] : []).map((origin) => origin?.DomainName).filter((domain) => typeof domain === "string" && domain !== "").map((domain) => maskSecrets(domain));
30900
+ const comment = typeof config["Comment"] === "string" ? maskSecrets(config["Comment"]) : "";
30901
+ const parts = [];
30902
+ if (domains.length > 0) parts.push(`origin domain ${domains.join(" / ")}`);
30903
+ if (comment !== "") parts.push(`comment ${JSON.stringify(comment)}`);
30904
+ return parts.length > 0 ? parts.join(" and ") : "its creation time (the orphan is the newest distribution in this account)";
30905
+ }
30906
+ /**
30785
30907
  * Update a CloudFront Distribution
30786
30908
  *
30787
30909
  * `UpdateDistribution` is a read-modify-write API that expects the COMPLETE
@@ -30938,11 +31060,14 @@ var CloudFrontDistributionProvider = class {
30938
31060
  * false positive every run (mirrors the Lambda `Code.S3*` precedent).
30939
31061
  *
30940
31062
  * - `DistributionConfig.CallerReference`: a create-time idempotency
30941
- * token. cdkd generates it internally (`<ts>-<logicalId>-<rand>`);
30942
- * AWS echoes it back, but it is never templated by CDK, so comparing
30943
- * it would diff cdkd's generated value on every run. `convertToCfnFormat`
30944
- * already drops it from the AWS side; declaring it here also excludes
30945
- * it from the observed-baseline side.
31063
+ * token. cdkd generates it internally (`acquireIdempotencyToken`, issue
31064
+ * #2079 a per-process digest, `cdkd-<sha256 prefix>`); AWS echoes it
31065
+ * back, but it is never templated by CDK, so comparing it would diff
31066
+ * cdkd's generated value on every run. It is not even stable across
31067
+ * cdkd RUNS by design (a process nonce is mixed in), so suppression is
31068
+ * required rather than merely convenient. `convertToCfnFormat` already
31069
+ * drops it from the AWS side; declaring it here also excludes it from
31070
+ * the observed-baseline side.
30946
31071
  * - `DistributionConfig.Logging.Bucket`: CloudFront normalizes the S3
30947
31072
  * logging bucket to its `<bucket>.s3.amazonaws.com` regional domain on
30948
31073
  * read, which never matches the bare bucket domain a template may
@@ -35329,13 +35454,23 @@ var ELBv2Provider = class {
35329
35454
  try {
35330
35455
  const listenerAttributes = this.normalizeAttributes(properties["ListenerAttributes"]);
35331
35456
  if (listenerAttributes.length > 0) {
35332
- await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
35333
- ListenerArn: listenerArn,
35334
- Attributes: listenerAttributes
35335
- })), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
35457
+ const watch = startInterruptWatch(`ELBv2 Listener ${logicalId} attributes`);
35458
+ try {
35459
+ await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
35460
+ ListenerArn: listenerArn,
35461
+ Attributes: listenerAttributes
35462
+ })), logicalId, {
35463
+ logger: this.maskedRetryLogger(maskSecrets),
35464
+ isInterrupted: watch.isInterrupted,
35465
+ onInterrupted: watch.onInterrupted
35466
+ });
35467
+ } finally {
35468
+ watch.dispose();
35469
+ }
35336
35470
  this.logger.debug(`Applied ${listenerAttributes.length} ListenerAttribute(s) for ${logicalId}`);
35337
35471
  }
35338
35472
  } catch (innerError) {
35473
+ if (isInterruptedWaitError(innerError)) this.logger.warn(maskerOrIdentity(maskSecrets)(`Interrupted after creating Listener ${logicalId} (${listenerArn}) but before its attributes were applied. Nothing in cdkd state refers to it, so cdkd is deleting it now — left behind it would fail the next deploy with DuplicateListener and neither rollback nor destroy could reach it. If that delete does not complete, remove it manually: aws elbv2 delete-listener --listener-arn ${listenerArn}`));
35339
35474
  try {
35340
35475
  await this.getClient().send(new DeleteListenerCommand({ ListenerArn: listenerArn }));
35341
35476
  this.logger.debug(`Cleaned up partially-created Listener ${logicalId} (${listenerArn}) after attributes-wiring failure`);
@@ -35372,10 +35507,19 @@ var ELBv2Provider = class {
35372
35507
  }));
35373
35508
  const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["ListenerAttributes"]), this.normalizeAttributes(previousProperties["ListenerAttributes"]), this.attributeRemovalResolver(LISTENER_ATTRIBUTE_DEFAULTS));
35374
35509
  if (submittedAttrs.length > 0) {
35375
- await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
35376
- ListenerArn: physicalId,
35377
- Attributes: submittedAttrs
35378
- })), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
35510
+ const watch = startInterruptWatch(`ELBv2 Listener ${logicalId} attributes`);
35511
+ try {
35512
+ await withRetry(() => this.getClient().send(new ModifyListenerAttributesCommand({
35513
+ ListenerArn: physicalId,
35514
+ Attributes: submittedAttrs
35515
+ })), logicalId, {
35516
+ logger: this.maskedRetryLogger(maskSecrets),
35517
+ isInterrupted: watch.isInterrupted,
35518
+ onInterrupted: watch.onInterrupted
35519
+ });
35520
+ } finally {
35521
+ watch.dispose();
35522
+ }
35379
35523
  this.logger.debug(`Applied ${submittedAttrs.length} ListenerAttributes change(s) for ${logicalId}`);
35380
35524
  }
35381
35525
  await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
@@ -42469,16 +42613,23 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
42469
42613
  this.logger.debug(`Deleting Cloud Map namespace ${logicalId}: ${physicalId}`);
42470
42614
  const client = this.getClient();
42471
42615
  try {
42472
- await withRetry(async () => {
42473
- const operationId = (await client.send(new DeleteNamespaceCommand({ Id: physicalId }))).OperationId;
42474
- if (operationId) await this.pollOperation(operationId, logicalId, resourceType);
42475
- }, logicalId, {
42476
- maxRetries: 24,
42477
- initialDelayMs: 3e3,
42478
- maxDelayMs: 15e3,
42479
- isRetryable: (message, error) => error instanceof ResourceInUse || /has associated services/i.test(message),
42480
- logger: this.logger
42481
- });
42616
+ const watch = startInterruptWatch(`Cloud Map namespace ${logicalId}`);
42617
+ try {
42618
+ await withRetry(async () => {
42619
+ const operationId = (await client.send(new DeleteNamespaceCommand({ Id: physicalId }))).OperationId;
42620
+ if (operationId) await this.pollOperation(operationId, logicalId, resourceType);
42621
+ }, logicalId, {
42622
+ maxRetries: 24,
42623
+ initialDelayMs: 3e3,
42624
+ maxDelayMs: 15e3,
42625
+ isRetryable: (message, error) => error instanceof ResourceInUse || /has associated services/i.test(message),
42626
+ logger: this.logger,
42627
+ isInterrupted: watch.isInterrupted,
42628
+ onInterrupted: watch.onInterrupted
42629
+ });
42630
+ } finally {
42631
+ watch.dispose();
42632
+ }
42482
42633
  this.logger.debug(`Successfully deleted Cloud Map namespace ${logicalId}`);
42483
42634
  } catch (error) {
42484
42635
  if (error instanceof NamespaceNotFound) {
@@ -42678,13 +42829,23 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
42678
42829
  try {
42679
42830
  const attrs = this.normalizeServiceAttributes(properties["ServiceAttributes"]);
42680
42831
  if (Object.keys(attrs).length > 0) {
42681
- await withRetry(() => client.send(new UpdateServiceAttributesCommand({
42682
- ServiceId: serviceId,
42683
- Attributes: attrs
42684
- })), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
42832
+ const watch = startInterruptWatch(`Cloud Map service ${logicalId} attributes`);
42833
+ try {
42834
+ await withRetry(() => client.send(new UpdateServiceAttributesCommand({
42835
+ ServiceId: serviceId,
42836
+ Attributes: attrs
42837
+ })), logicalId, {
42838
+ logger: this.maskedRetryLogger(maskSecrets),
42839
+ isInterrupted: watch.isInterrupted,
42840
+ onInterrupted: watch.onInterrupted
42841
+ });
42842
+ } finally {
42843
+ watch.dispose();
42844
+ }
42685
42845
  this.logger.debug(`Applied ${Object.keys(attrs).length} ServiceAttribute(s) for ${logicalId}`);
42686
42846
  }
42687
42847
  } catch (innerError) {
42848
+ if (isInterruptedWaitError(innerError)) this.logger.warn(maskerOrIdentity(maskSecrets)(`Interrupted after creating ServiceDiscovery Service ${logicalId} (${serviceId}) but before its ServiceAttributes were applied. Nothing in cdkd state refers to it, so cdkd is deleting it now — left behind it would fail the next deploy on a name collision AND block deletion of its namespace with ResourceInUse. If that delete does not complete, remove it manually: aws servicediscovery delete-service --id ${serviceId}`));
42688
42849
  try {
42689
42850
  await client.send(new DeleteServiceCommand$1({ Id: serviceId }));
42690
42851
  this.logger.debug(`Cleaned up partially-created ServiceDiscovery Service ${logicalId} (${serviceId}) after ServiceAttributes wiring failure`);
@@ -42754,19 +42915,34 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
42754
42915
  }))).OperationId;
42755
42916
  if (operationId) await this.pollOperation(operationId, logicalId, resourceType, maskSecrets);
42756
42917
  }
42757
- if (hasAttrUpsert) {
42758
- await withRetry(() => client.send(new UpdateServiceAttributesCommand({
42759
- ServiceId: physicalId,
42760
- Attributes: upsertAttrs
42761
- })), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
42762
- this.logger.debug(`Applied ${Object.keys(upsertAttrs).length} ServiceAttribute change(s) for ${logicalId}`);
42763
- }
42764
- if (hasAttrRemove) {
42765
- await withRetry(() => client.send(new DeleteServiceAttributesCommand({
42766
- ServiceId: physicalId,
42767
- Attributes: removedAttrKeys
42768
- })), logicalId, { logger: this.maskedRetryLogger(maskSecrets) });
42769
- this.logger.debug(`Removed ${removedAttrKeys.length} ServiceAttribute(s) for ${logicalId}`);
42918
+ if (hasAttrUpsert || hasAttrRemove) {
42919
+ const attrWatch = startInterruptWatch(`Cloud Map service ${logicalId} attributes`);
42920
+ try {
42921
+ if (hasAttrUpsert) {
42922
+ await withRetry(() => client.send(new UpdateServiceAttributesCommand({
42923
+ ServiceId: physicalId,
42924
+ Attributes: upsertAttrs
42925
+ })), logicalId, {
42926
+ logger: this.maskedRetryLogger(maskSecrets),
42927
+ isInterrupted: attrWatch.isInterrupted,
42928
+ onInterrupted: attrWatch.onInterrupted
42929
+ });
42930
+ this.logger.debug(`Applied ${Object.keys(upsertAttrs).length} ServiceAttribute change(s) for ${logicalId}`);
42931
+ }
42932
+ if (hasAttrRemove) {
42933
+ await withRetry(() => client.send(new DeleteServiceAttributesCommand({
42934
+ ServiceId: physicalId,
42935
+ Attributes: removedAttrKeys
42936
+ })), logicalId, {
42937
+ logger: this.maskedRetryLogger(maskSecrets),
42938
+ isInterrupted: attrWatch.isInterrupted,
42939
+ onInterrupted: attrWatch.onInterrupted
42940
+ });
42941
+ this.logger.debug(`Removed ${removedAttrKeys.length} ServiceAttribute(s) for ${logicalId}`);
42942
+ }
42943
+ } finally {
42944
+ attrWatch.dispose();
42945
+ }
42770
42946
  }
42771
42947
  this.logger.debug(`Successfully updated service discovery service ${logicalId}`);
42772
42948
  return {
@@ -58128,7 +58304,7 @@ async function runDestroyForStack(stackName, state, ctx) {
58128
58304
  } catch (error) {
58129
58305
  renderer.removeTask(logicalId);
58130
58306
  const msg = error instanceof Error ? error.message : String(error);
58131
- if (!isFinalSnapshotError(error) && (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException"))) {
58307
+ if (!isInterruptedWaitError(error) && !isFinalSnapshotError(error) && (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException"))) {
58132
58308
  logger.debug(` ${logicalId} already deleted, removing from state`);
58133
58309
  result.deletedCount++;
58134
58310
  ctx.eventRecorder?.record({
@@ -58212,11 +58388,19 @@ async function runDestroyForStack(stackName, state, ctx) {
58212
58388
  logger.warn(`\n${yellow("⚠")} ${bold(`Stack ${stackName} partially destroyed`)} (${green(result.deletedCount)} deleted${retainedSuffix}${skippedSuffix}, ${red(result.errorCount)} errors). State preserved — re-run 'cdkd destroy' / 'cdkd state destroy' to clean up. If the same resource keeps failing, ${orphanHint} is the last resort: it removes the state record without deleting AWS resources.` + skippedClause);
58213
58389
  }
58214
58390
  } finally {
58215
- process.removeListener("SIGINT", sigintHandler);
58216
- renderer.stop();
58217
- await saveChain;
58218
- logger.debug("Releasing lock...");
58219
- await ctx.lockManager.releaseLock(stackName, regionForState);
58391
+ try {
58392
+ try {
58393
+ renderer.stop();
58394
+ } catch (rendererError) {
58395
+ logger.debug(`Live renderer teardown failed (continuing to release the lock): ${rendererError instanceof Error ? rendererError.message : String(rendererError)}`);
58396
+ }
58397
+ await saveChain;
58398
+ logger.debug("Releasing lock...");
58399
+ await ctx.lockManager.releaseLock(stackName, regionForState);
58400
+ } finally {
58401
+ process.removeListener("SIGINT", sigintHandler);
58402
+ }
58403
+ result.interrupted ||= draining;
58220
58404
  if (destroyAwsClients) {
58221
58405
  destroyAwsClients.destroy();
58222
58406
  process.env["AWS_REGION"] = ctx.baseRegion;
@@ -83844,7 +84028,7 @@ function createMigrateCommand() {
83844
84028
  */
83845
84029
  function buildProgram() {
83846
84030
  const program = new Command();
83847
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.23");
84031
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.25");
83848
84032
  program.hook("preAction", (_thisCommand, actionCommand) => {
83849
84033
  const { profile } = actionCommand.optsWithGlobals();
83850
84034
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;