@go-to-k/cdkd 0.284.15 → 0.284.17

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.
@@ -346,16 +346,27 @@ function isThrottlingError(error) {
346
346
  *
347
347
  * ACCEPTED RISK, stated rather than discovered later: this makes a
348
348
  * NON-IDEMPOTENT create retryable on a 500 that may have succeeded
349
- * server-side. `EC2Provider.createInstance` issues `RunInstances` with no
350
- * `ClientToken` (only four providers use one at all), and
351
- * `IAMAccessKeyProvider` mints an unnamed key, so a replay can leave a
352
- * resource that is absent from state and therefore from destroy. The class is
353
- * PRE-EXISTING -- the SDK's own three attempts already reach it, and 503 was
354
- * already retryable here -- but this widens the window from ~1s to the full
355
- * schedule. Judged worth it because the alternative is the measured failure
356
- * (a deploy that dies outright on a transient 500), and because the durable
357
- * remedy is per-provider idempotency tokens rather than a blanket refusal to
358
- * retry server errors. Tracked in issue #2039.
349
+ * server-side, so a replay can leave a resource that is absent from state and
350
+ * therefore from destroy. The class is PRE-EXISTING -- the SDK's own three
351
+ * attempts already reach it, and 503 was already retryable here -- but this
352
+ * widens the window from ~1s to the full schedule. Judged worth it because the
353
+ * alternative is the measured failure (a deploy that dies outright on a
354
+ * transient 500), and because the durable remedy is per-provider idempotency
355
+ * tokens rather than a blanket refusal to retry server errors.
356
+ *
357
+ * STATUS (issue #2039, and read this before citing the paragraph above). The
358
+ * two worked examples this note used to carry -- `RunInstances` sent with no
359
+ * `ClientToken`, and `IAMAccessKeyProvider` minting an unnamed key -- are both
360
+ * FIXED, so quoting them as live hazards would now mislead. `RunInstances`,
361
+ * `CreateNatGateway`, `CreateRouteTable`, `CreateNetworkAcl` and
362
+ * `CreateHostedZone` carry a retry-stable token from
363
+ * `src/provisioning/providers/idempotency-token.ts`, and `CreateAccessKey`
364
+ * (which has no token member) reconciles the orphan its own failed attempt
365
+ * left. The claim that "only four providers use one at all" was also wrong when
366
+ * written: six did, and two of those regenerated the token per attempt, which
367
+ * is worse than none. What REMAINS accepted here is the residue -- roughly 25
368
+ * creates across 16 providers audited in issue #2039 and enumerated in issue
369
+ * #2080 -- so this set stays as-is and the remedy stays per-provider.
359
370
  */
360
371
  const TRANSIENT_SERVER_ERROR_STATUS_CODES = /* @__PURE__ */ new Set([
361
372
  500,
@@ -16503,7 +16514,7 @@ var CloudControlProvider = class {
16503
16514
  if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
16504
16515
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
16505
16516
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
16506
- const { ASGProvider } = await import("./asg-provider-BZwQzb8e.js").then((n) => n.n);
16517
+ const { ASGProvider } = await import("./asg-provider-BbLu5LGL.js").then((n) => n.n);
16507
16518
  return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
16508
16519
  }
16509
16520
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -24023,6 +24034,91 @@ async function updateWithRollbackRetry(provider, args, logicalId, logger, isInte
24023
24034
  });
24024
24035
  }
24025
24036
  /**
24037
+ * Both retry loops around a reverse-replacement replay-CREATE (issue
24038
+ * [#2032](https://github.com/go-to-k/cdkd/issues/2032)) — the create-side twin
24039
+ * of {@link updateWithRollbackRetry}, and the single place the two replay arms
24040
+ * get their `disableOuterRetry` guard.
24041
+ *
24042
+ * ## The nesting, and why it is required
24043
+ *
24044
+ * A caller-supplied `isRetryable` REPLACES `isRetryableTransientError`
24045
+ * outright, and ANY explicit schedule knob sets `defaultSchedule = false` in
24046
+ * `retry.ts`, which is the gate on the dense IAM-propagation path. Both
24047
+ * replay-CREATE arms pass BOTH ({@link RECREATE_RETRY_SCHEDULE} plus
24048
+ * `isNameCooldownError` / `isRecreateRetryableError`), so a propagation error
24049
+ * raised by the re-create — the old execution role was re-created moments
24050
+ * earlier in this same rollback, so `CreateFunction` answers `The role defined
24051
+ * for the function cannot be assumed by Lambda.` — was non-retryable on
24052
+ * attempt 0 and rethrown raw, leaving the resource absent from BOTH AWS and
24053
+ * state. The INNER call passes NO knobs and NO classifier, so it gets the
24054
+ * dense 26-retry / 47.75s propagation schedule while the OUTER one keeps
24055
+ * owning the name-release cadence.
24056
+ *
24057
+ * ## What the deploy engine's precedents actually are
24058
+ *
24059
+ * They are two DIFFERENT shapes, and the two rollback arms need one each —
24060
+ * this helper is deliberately the sum of both rather than a copy of either:
24061
+ *
24062
+ * - Arm 2 (post-delete-new-first) matches the delete-then-re-create sites,
24063
+ * `deploy-engine.ts`'s `--replace` delete-first fallback and its named
24064
+ * replacement, which nest `this.withRetry(...)` INSIDE an outer
24065
+ * `isRecreateRetryableError` retry. Same two loops as here.
24066
+ * - Arm 1 (create-first) has NO such twin. Its deploy-engine analogue is the
24067
+ * property-driven create-first at `deploy-engine.ts:3745`, which calls
24068
+ * `this.withRetry(...)` on its OWN — one default-schedule loop, no outer
24069
+ * custom-classifier loop at all — and whose catch then reads
24070
+ * `isNameCollisionError` to reach the delete-first fallback. Arm 1 is that
24071
+ * shape PLUS the outer SQS-cooldown loop issue #1206 added, so it is the
24072
+ * SUM of both precedents.
24073
+ *
24074
+ * ## Why the guard lives here and not in `retry.ts`
24075
+ *
24076
+ * `withRetry` never receives the provider, so it cannot honour
24077
+ * `disableOuterRetry` — re-running `CustomResourceProvider.create()` /
24078
+ * `NestedStackProvider.create()` mints a fresh pre-signed S3 URL + RequestId
24079
+ * and strands the previous attempt at a key nobody polls, and re-running
24080
+ * `NestedStackProvider.create()` re-creates child stacks and child state
24081
+ * files. That check therefore has to live next to the provider, exactly as
24082
+ * `DeployEngine.withRetry` does it.
24083
+ *
24084
+ * The guard covers BOTH loops, not just the inner one. Guarding only the inner
24085
+ * loop left the outer schedule free to re-enter, which measured at 9
24086
+ * `create()` calls for a cooldown and 10 for a collision against an opt-out
24087
+ * provider — i.e. the exact hazard the flag exists for, arriving through the
24088
+ * outer loop instead. A single-shot call still lets a name collision reach the
24089
+ * CALLER's catch on attempt 0 (that catch sits outside this helper), so the
24090
+ * delete-new-first fallback is unaffected by the opt-out.
24091
+ *
24092
+ * ## The collision arm is deliberately untouched
24093
+ *
24094
+ * `isNameCollisionError`'s signature (`already exist(s)` / `AlreadyExists`) is
24095
+ * NOT in `RETRYABLE_ERROR_MESSAGE_PATTERNS`, so the inner classifier rejects it
24096
+ * on attempt 0 and it reaches the caller's catch on the FIRST outer attempt,
24097
+ * exactly as before. The SQS cooldown IS matched by the inner classifier (the
24098
+ * generic table carries `wait 60 seconds`), which is the same division of
24099
+ * labour the deploy engine's named-replacement site documents: the inner retry
24100
+ * absorbs most of the 60s window and the outer ~64s budget covers the tail.
24101
+ */
24102
+ async function createWithRollbackRetry(provider, create, logicalId, logger, isInterrupted, secrets, outer) {
24103
+ if (provider.disableOuterRetry) return await create();
24104
+ const maskedLogger = maskingRetryLogger(logger, secrets);
24105
+ return await withRetry(() => withRetry(create, logicalId, {
24106
+ logger: maskedLogger,
24107
+ ...isInterrupted && {
24108
+ isInterrupted,
24109
+ onInterrupted: () => /* @__PURE__ */ new Error("Rollback interrupted while retrying the replay re-create")
24110
+ }
24111
+ }), logicalId, {
24112
+ ...RECREATE_RETRY_SCHEDULE,
24113
+ logger: maskedLogger,
24114
+ ...isInterrupted && {
24115
+ isInterrupted,
24116
+ onInterrupted: () => new Error(outer.interruptedMessage)
24117
+ },
24118
+ isRetryable: outer.isRetryable
24119
+ });
24120
+ }
24121
+ /**
24026
24122
  * The state record to store after a rollback UPDATE arm (issue #1644).
24027
24123
  *
24028
24124
  * The bag handed to `update()` on both arms IS `restored.properties`, so a
@@ -24222,14 +24318,9 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
24222
24318
  let deletedNewFirst = false;
24223
24319
  let createResult;
24224
24320
  try {
24225
- createResult = await withRetry(() => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets)), op.logicalId, {
24226
- ...RECREATE_RETRY_SCHEDULE,
24227
- logger: maskingRetryLogger(logger, secrets),
24228
- ...isInterrupted && {
24229
- isInterrupted,
24230
- onInterrupted: () => /* @__PURE__ */ new Error("Rollback interrupted while waiting out the name cooldown")
24231
- },
24232
- isRetryable: isNameCooldownError
24321
+ createResult = await createWithRollbackRetry(createProvider, () => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets)), op.logicalId, logger, isInterrupted, secrets, {
24322
+ isRetryable: isNameCooldownError,
24323
+ interruptedMessage: "Rollback interrupted while waiting out the name cooldown"
24233
24324
  });
24234
24325
  } catch (createError) {
24235
24326
  if (!isNameCollisionError(createError instanceof Error ? createError.message : String(createError))) throw createError;
@@ -24245,14 +24336,9 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
24245
24336
  delete stateResources[op.logicalId];
24246
24337
  await afterOp?.(op.logicalId);
24247
24338
  try {
24248
- createResult = await withRetry(() => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets)), op.logicalId, {
24249
- ...RECREATE_RETRY_SCHEDULE,
24250
- logger: maskingRetryLogger(logger, secrets),
24251
- ...isInterrupted && {
24252
- isInterrupted,
24253
- onInterrupted: () => /* @__PURE__ */ new Error("Rollback interrupted while waiting for the old name to release")
24254
- },
24255
- isRetryable: isRecreateRetryableError
24339
+ createResult = await createWithRollbackRetry(createProvider, () => createProvider.create(op.logicalId, op.resourceType, { ...resolvedPrevProps }, replayingStateCreateContext(secrets)), op.logicalId, logger, isInterrupted, secrets, {
24340
+ isRetryable: isRecreateRetryableError,
24341
+ interruptedMessage: "Rollback interrupted while waiting for the old name to release"
24256
24342
  });
24257
24343
  } catch (recreateError) {
24258
24344
  throw new Error(maskSecretsInText(`Failed to re-create the old ${op.logicalId} after the new resource (${current.physicalId}) was already deleted: ${recreateError instanceof Error ? recreateError.message : String(recreateError)}. The resource is now absent — fix forward with 'cdkd deploy'.`, secrets));
@@ -24601,7 +24687,7 @@ const FLUSH_INTERVAL_MS = 2e3;
24601
24687
  const FLUSH_EVENT_THRESHOLD = 50;
24602
24688
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
24603
24689
  function getCdkdVersion() {
24604
- return "0.284.15";
24690
+ return "0.284.17";
24605
24691
  }
24606
24692
  /**
24607
24693
  * Generate a time-sortable unique run id, e.g.
@@ -27141,4 +27227,4 @@ var DeployEngine = class {
27141
27227
 
27142
27228
  //#endregion
27143
27229
  export { isTerminationProtectionPropagationError as $, ResourceUpdateNotSupportedError as $n, validateAssetBucketName as $t, renderStatefulReason as A, derivePartitionAndUrlSuffix as An, INTRINSIC_KEYS as At, exportAliasCollisionScrubWarning as B, CdkdError as Bn, stringifyValue as Bt, isFinalSnapshotError as C, CFN_TEMPLATE_URL_LIMIT as Cn, s3BucketArn as Ct, extractDeploymentEventError as D, expectedOwnerParam as Dn, s3BucketWebsiteUrl as Dt, makeCanonicalizePropertiesFn as E, uploadCfnTemplate as En, s3BucketRegionalDomainName as Et, green as F, AwsClients as Fn, LockManager as Ft, collectInlinePolicyNamesManagedBySiblings as G, LocalMigrateError as Gn, rewriteTemplateAssetReferences as Gt, secretBearingStateKeyWarning as H, DependencyError as Hn, buildAssetRedirectMap as Ht, red as I, getAwsClients as In, S3StateBackend as It, findActionableSilentDrops as J, MissingCdkCliError as Jn, AssetModeResolver as Jt, clearOnUpdateRemoval as K, LocalStartServiceError as Kn, escapeRegExp$1 as Kt, yellow as L, resetAwsClients as Ln, rebuildClientForBucketRegion as Lt, bold as M, processStackMessages as Mn, withRetry as Mt, cyan as N, clearBucketRegionCache as Nn, DagBuilder as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, PARTITION_TABLE as On, applyRoleArnIfSet as Ot, gray as P, resolveBucketRegion as Pn, TemplateParser as Pt, disableInstanceApiTermination as Q, ResourceTimeoutError as Qn, parseBootstrapMarker as Qt, collectDeclaredOutputNames as R, setAwsClients as Rn, shouldRetainResource as Rt, createPreDeleteFinalSnapshot as S, CFN_TEMPLATE_BODY_LIMIT as Sn, scrubResourceRecord as St, unsupportedFinalSnapshotError as T, findLargeInlineResources as Tn, s3BucketDualStackDomainName as Tt, stateKeySecretExposure as U, DeployCancelledError as Un, createAssetRedirectResolver as Ut, isExportAliasCollision as V, ConfigError as Vn, WorkGraph as Vt, IAMRoleProvider as W, LocalInvokeBuildError as Wn, loadPublishableAssetManifest as Wt, CloudControlProvider as X, PartialFailureError as Xn, ensureAssetStorage as Xt, findSilentDropProperties as Y, NestedStackChildDirectDestroyError as Yn, BOOTSTRAP_MARKER_PREFIX as Yt, slowCcOperationTimeoutMs as Z, ProvisioningError as Zn, getBootstrapMarkerKey as Zt, computeImplicitDeleteEdges as _, resolveStateBucketWithDefault as _n, STATE_SOURCED_READBACK_RULES as _t, DeploymentEventsStore as a, runDockerForeground as an, isCdkdError as ar, normalizeAwsTagsToCfn as at, buildFinalSnapshotIdentifier as b, stateBucketExistenceConfirmed as bn, maskSecretsInText as bt, replayFailedOperations as c, getDockerImageBySourceHash as cn, isMarkedNonRetryable as cr, coerceCfnBoolean as ct, updatePartialReason as d, getDefaultStateBucketName as dn, markNonRetryable as dr, readConfigString as dt, validateContainerRepoName as en, StackHasActiveImportsError as er, IntrinsicFunctionResolver as et, UNSPECIFIED_SKIP_REASON as f, getLegacyStateBucketName as fn, __exportAll as fr, replayWarn as ft, IMPLICIT_DELETE_DEPENDENCIES as g, resolveSkipPrefix as gn, STATE_SOURCED_CROSS_GENERATION_RULES as gt, maskingRetryLogger as h, resolveCaptureObservedState as hn, requireConfigString as ht, DeploymentEventsReader as i, getDockerCmd as in, formatError as ir, WAFv2WebACLProvider as it, formatResourceLine as j, AssemblyReader as jn, describeTypeWithThrottleRetry as jt, isStatefulRecreateTargetSync as k, canonicalizeRegion as kn, DiffCalculator as kt, replayRollback as l, Synthesizer as ln, isRetryableTransientError as lr, configBooleanRefusal as lt, withResourceDeadline as m, resolveAutoAssetStorage as mn, requireConfigObject as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, buildDockerImage as nn, StateError as nr, getAccountInfo as nt, planFailedOps as o, runDockerStreaming as on, normalizeAwsError as or, resolveExplicitPhysicalId as ot, deleteSkipReason as p, resolveApp as pn, requireConfigArray as pt, ProviderRegistry as q, LockError as qn, stripControlChars as qt, DeployEngine as r, formatDockerLoginError as rn, SynthesisError as rr, refStateLookupFromResource as rt, planRollback as s, AssetManifestLoader as sn, withErrorHandling as sr, assertRegionMatch as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, buildDenyExternalAccessPolicy as tn, StackTerminationProtectionError as tr, cfnRefValueFromPhysicalId as tt, updatePartialMessage as u, synthesisStatusMessage as un, isThrottlingError as ur, configStringRefusal as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, resolveStateBucketWithDefaultAndSource as vn, TEMPLATE_SOURCED_RULES as vt, refusesFinalSnapshot as w, MIGRATE_TMP_PREFIX as wn, s3BucketDomainName as wt, ccRoutedFinalSnapshotError as x, warnDeprecatedNoPrefixCliFlag as xn, redactSecretsForState as xt, PRE_DELETE_SNAPSHOT_TYPES as y, resolveUseCdkBootstrapAssets as yn, createSecretMasker as yt, collectPublishedOutputNames as z, AssetError as zn, AssetPublisher as zt };
27144
- //# sourceMappingURL=deploy-engine-BNujuzW5.js.map
27230
+ //# sourceMappingURL=deploy-engine-Bnu1WuRI.js.map