@go-to-k/cdkd 0.283.36 → 0.284.1

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 getAccountInfo, $t as formatDockerLoginError, A as cyan, An as resolveBucketRegion, At as TemplateParser, B as stateKeySecretExposure, Bn as LocalInvokeBuildError$1, Bt as loadPublishableAssetManifest, C as makeCanonicalizePropertiesFn, Cn as expectedOwnerParam, Ct as s3BucketWebsiteUrl, D as renderStatefulReason, Dn as AssemblyReader, Dt as describeTypeWithThrottleRetry, E as isStatefulRecreateTargetSync, En as derivePartitionAndUrlSuffix, Et as INTRINSIC_KEYS, F as collectDeclaredOutputNames, Ft as AssetPublisher, G as findActionableSilentDrops, Gn as NestedStackChildDirectDestroyError, Gt as BOOTSTRAP_MARKER_PREFIX, H as collectInlinePolicyNamesManagedBySiblings, Hn as LocalStartServiceError, Ht as escapeRegExp, I as collectPublishedOutputNames, In as CdkdError, It as stringifyValue, J as slowCcOperationTimeoutMs, Jn as ResourceTimeoutError, Jt as parseBootstrapMarker, K as findSilentDropProperties, Kn as PartialFailureError, Kt as ensureAssetStorage, L as exportAliasCollisionScrubWarning, Lt as WorkGraph, M as green, Mn as getAwsClients, Mt as S3StateBackend, N as red, Nn as resetAwsClients, Nt as rebuildClientForBucketRegion, O as formatResourceLine, On as processStackMessages, Ot as withRetry, P as yellow, Pn as setAwsClients, Pt as shouldRetainResource, Q as cfnRefValueFromPhysicalId, Qt as buildDockerImage, R as isExportAliasCollision, Rt as buildAssetRedirectMap, S as unsupportedFinalSnapshotError, Sn as uploadCfnTemplate, St as s3BucketRegionalDomainName, T as MULTI_REGION_RECREATE_BLOCKED_TYPES, Tn as canonicalizeRegion, Tt as DiffCalculator, U as clearOnUpdateRemoval, Ut as stripControlChars, V as IAMRoleProvider, Vn as LocalMigrateError, Vt as rewriteTemplateAssetReferences, W as ProviderRegistry, Wn as MissingCdkCliError, Wt as AssetModeResolver, X as isTerminationProtectionPropagationError, Xn as StackHasActiveImportsError, Xt as validateContainerRepoName, Y as disableInstanceApiTermination, Yn as ResourceUpdateNotSupportedError, Yt as validateAssetBucketName, Z as IntrinsicFunctionResolver, Zn as StackTerminationProtectionError, Zt as buildDenyExternalAccessPolicy, _ as buildFinalSnapshotIdentifier, _n as warnDeprecatedNoPrefixCliFlag, _t as redactSecretsForState, a as DeploymentEventsStore, an as Synthesizer, ar as isRetryableTransientError, at as coerceCfnBoolean, b as isFinalSnapshotError, bn as MIGRATE_TMP_PREFIX, bt as s3BucketDomainName, c as replayFailedOperations, cn as getLegacyStateBucketName, ct as readConfigString, d as deleteSkipReason, dn as resolveCaptureObservedState, dt as requireConfigObject, en as getDockerCmd, et as refStateLookupFromResource, f as withResourceDeadline, fn as resolveSkipPrefix, ft as requireConfigString, g as PRE_DELETE_SNAPSHOT_TYPES, gn as stateBucketExistenceConfirmed, gt as maskSecretsInText, h as ATOMIC_FINAL_SNAPSHOT_TYPES, hn as resolveUseCdkBootstrapAssets, ht as TEMPLATE_SOURCED_RULES, i as DeploymentEventsReader, in as getDockerImageBySourceHash, ir as isMarkedNonRetryable, it as assertRegionMatch, j as gray, jn as AwsClients, jt as LockManager, k as bold, kt as DagBuilder, l as replayRollback, ln as resolveApp, lt as replayWarn, m as computeImplicitDeleteEdges, mn as resolveStateBucketWithDefaultAndSource, mt as STATE_SOURCED_READBACK_RULES, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as runDockerStreaming, nr as normalizeAwsError, nt as normalizeAwsTagsToCfn, o as planFailedOps, on as synthesisStatusMessage, or as isThrottlingError, ot as configBooleanRefusal, p as IMPLICIT_DELETE_DEPENDENCIES, pn as resolveStateBucketWithDefault, pt as STATE_SOURCED_CROSS_GENERATION_RULES, q as CloudControlProvider, qn as ProvisioningError, qt as getBootstrapMarkerKey, r as DeployEngine, rn as AssetManifestLoader, rr as withErrorHandling, rt as resolveExplicitPhysicalId, s as planRollback, sn as getDefaultStateBucketName, sr as markNonRetryable, st as configStringRefusal, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as runDockerForeground, tt as WAFv2WebACLProvider, u as UNSPECIFIED_SKIP_REASON, un as resolveAutoAssetStorage, ut as requireConfigArray, v as ccRoutedFinalSnapshotError, vn as CFN_TEMPLATE_BODY_LIMIT, vt as scrubResourceRecord, w as extractDeploymentEventError, wn as PARTITION_TABLE, wt as applyRoleArnIfSet, x as refusesFinalSnapshot, xn as findLargeInlineResources, xt as s3BucketDualStackDomainName, y as createPreDeleteFinalSnapshot, yn as CFN_TEMPLATE_URL_LIMIT, yt as s3BucketArn, z as secretBearingStateKeyWarning, zn as DeployCancelledError, zt as createAssetRedirectResolver } from "./deploy-engine-Bhq_JPCc.js";
2
+ import { $ as IntrinsicFunctionResolver, $n as StackTerminationProtectionError, $t as buildDenyExternalAccessPolicy, A as formatResourceLine, An as processStackMessages, At as withRetry, B as isExportAliasCollision, Bt as buildAssetRedirectMap, C as refusesFinalSnapshot, Cn as findLargeInlineResources, Ct as s3BucketDualStackDomainName, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as canonicalizeRegion, Dt as DiffCalculator, E as extractDeploymentEventError, En as PARTITION_TABLE, Et as applyRoleArnIfSet, F as red, Fn as resetAwsClients, Ft as rebuildClientForBucketRegion, G as clearOnUpdateRemoval, Gt as stripControlChars, H as stateKeySecretExposure, Hn as LocalInvokeBuildError$1, Ht as loadPublishableAssetManifest, I as yellow, In as setAwsClients, It as shouldRetainResource, J as findSilentDropProperties, Jn as PartialFailureError, Jt as ensureAssetStorage, K as ProviderRegistry, Kn as MissingCdkCliError, Kt as AssetModeResolver, L as collectDeclaredOutputNames, Lt as AssetPublisher, M as cyan, Mn as resolveBucketRegion, Mt as TemplateParser, N as gray, Nn as AwsClients, Nt as LockManager, O as isStatefulRecreateTargetSync, On as derivePartitionAndUrlSuffix, Ot as INTRINSIC_KEYS, P as green, Pn as getAwsClients, Pt as S3StateBackend, Q as isTerminationProtectionPropagationError, Qn as StackHasActiveImportsError, Qt as validateContainerRepoName, R as collectPublishedOutputNames, Rn as CdkdError, Rt as stringifyValue, S as isFinalSnapshotError, Sn as MIGRATE_TMP_PREFIX, St as s3BucketDomainName, T as makeCanonicalizePropertiesFn, Tn as expectedOwnerParam, Tt as s3BucketWebsiteUrl, U as IAMRoleProvider, Un as LocalMigrateError, Ut as rewriteTemplateAssetReferences, V as secretBearingStateKeyWarning, Vn as DeployCancelledError, Vt as createAssetRedirectResolver, W as collectInlinePolicyNamesManagedBySiblings, Wn as LocalStartServiceError, Wt as escapeRegExp, X as slowCcOperationTimeoutMs, Xn as ResourceTimeoutError, Xt as parseBootstrapMarker, Y as CloudControlProvider, Yn as ProvisioningError, Yt as getBootstrapMarkerKey, Z as disableInstanceApiTermination, Zn as ResourceUpdateNotSupportedError, Zt as validateAssetBucketName, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _n as resolveUseCdkBootstrapAssets, _t as TEMPLATE_SOURCED_RULES, a as DeploymentEventsStore, an as AssetManifestLoader, ar as withErrorHandling, at as resolveExplicitPhysicalId, b as ccRoutedFinalSnapshotError, bn as CFN_TEMPLATE_BODY_LIMIT, bt as scrubResourceRecord, c as replayFailedOperations, cn as synthesisStatusMessage, cr as isThrottlingError, ct as configBooleanRefusal, d as updatePartialReason, dn as resolveApp, dt as replayWarn, en as buildDockerImage, et as cfnRefValueFromPhysicalId, f as UNSPECIFIED_SKIP_REASON, fn as resolveAutoAssetStorage, ft as requireConfigArray, g as computeImplicitDeleteEdges, gn as resolveStateBucketWithDefaultAndSource, gt as STATE_SOURCED_READBACK_RULES, h as IMPLICIT_DELETE_DEPENDENCIES, hn as resolveStateBucketWithDefault, ht as STATE_SOURCED_CROSS_GENERATION_RULES, i as DeploymentEventsReader, in as runDockerStreaming, ir as normalizeAwsError, it as normalizeAwsTagsToCfn, j as bold, jt as DagBuilder, k as renderStatefulReason, kn as AssemblyReader, kt as describeTypeWithThrottleRetry, l as replayRollback, ln as getDefaultStateBucketName, lr as markNonRetryable, lt as configStringRefusal, m as withResourceDeadline, mn as resolveSkipPrefix, mt as requireConfigString, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as getDockerCmd, nt as refStateLookupFromResource, o as planFailedOps, on as getDockerImageBySourceHash, or as isMarkedNonRetryable, ot as assertRegionMatch, p as deleteSkipReason, pn as resolveCaptureObservedState, pt as requireConfigObject, q as findActionableSilentDrops, qn as NestedStackChildDirectDestroyError, qt as BOOTSTRAP_MARKER_PREFIX, r as DeployEngine, rn as runDockerForeground, rt as WAFv2WebACLProvider, s as planRollback, sn as Synthesizer, sr as isRetryableTransientError, st as coerceCfnBoolean, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as formatDockerLoginError, tt as getAccountInfo, u as updatePartialMessage, un as getLegacyStateBucketName, ut as readConfigString, v as PRE_DELETE_SNAPSHOT_TYPES, vn as stateBucketExistenceConfirmed, vt as maskSecretsInText, w as unsupportedFinalSnapshotError, wn as uploadCfnTemplate, wt as s3BucketRegionalDomainName, x as createPreDeleteFinalSnapshot, xn as CFN_TEMPLATE_URL_LIMIT, xt as s3BucketArn, y as buildFinalSnapshotIdentifier, yn as warnDeprecatedNoPrefixCliFlag, yt as redactSecretsForState, z as exportAliasCollisionScrubWarning, zt as WorkGraph } from "./deploy-engine-CruC7Qxn.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, 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-BruE_3F2.js";
4
+ import { t as ASGProvider } from "./asg-provider-DDEsD6uA.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";
@@ -3098,18 +3098,27 @@ var IAMManagedPolicyProvider = class {
3098
3098
  const reason = newPolicyName !== oldPolicyName ? "ManagedPolicyName" : newPath !== oldPath ? "Path" : "Description";
3099
3099
  this.logger.debug(`${reason} changed, replacing managed policy: ${physicalId} (${reason} mutation)`);
3100
3100
  const createResult = await this.create(logicalId, resourceType, properties);
3101
+ let orphanReason;
3101
3102
  try {
3102
3103
  const deleteResult = await this.delete(logicalId, physicalId, resourceType, previousProperties);
3103
- if (deleteResult?.outcome === "skipped") this.logger.warn(`Skipped deleting old managed policy ${physicalId} during replacement: ${deleteResult.reason}. The old policy may be orphaned and require manual cleanup.`);
3104
+ if (deleteResult?.outcome === "skipped") {
3105
+ orphanReason = `old managed policy ${physicalId} was not deleted: ${deleteResult.reason}`;
3106
+ this.logger.warn(`Skipped deleting old managed policy ${physicalId} during replacement: ${deleteResult.reason}. The old policy may be orphaned and require manual cleanup.`);
3107
+ }
3104
3108
  } catch (error) {
3109
+ orphanReason = `old managed policy ${physicalId} could not be deleted: ${String(error)}`;
3105
3110
  this.logger.warn(`Failed to delete old managed policy ${physicalId} during replacement: ${String(error)}. The old policy may be orphaned and require manual cleanup.`);
3106
3111
  }
3107
- const result = {
3112
+ const base = {
3108
3113
  physicalId: createResult.physicalId,
3109
- wasReplaced: true
3114
+ wasReplaced: true,
3115
+ ...createResult.attributes ? { attributes: createResult.attributes } : {}
3110
3116
  };
3111
- if (createResult.attributes) result.attributes = createResult.attributes;
3112
- return result;
3117
+ return orphanReason !== void 0 ? {
3118
+ ...base,
3119
+ outcome: "partial",
3120
+ reason: orphanReason
3121
+ } : base;
3113
3122
  }
3114
3123
  try {
3115
3124
  const newDocument = properties["PolicyDocument"];
@@ -26244,16 +26253,23 @@ var ApiGatewayProvider = class ApiGatewayProvider {
26244
26253
  if (newPathPart !== oldPathPart) {
26245
26254
  this.logger.debug(`PathPart changed from "${oldPathPart}" to "${newPathPart}", replacing resource`);
26246
26255
  const createResult = await this.createResource(logicalId, resourceType, properties);
26256
+ let orphanReason;
26247
26257
  try {
26248
26258
  await this.deleteResource(logicalId, physicalId, resourceType, previousProperties);
26249
26259
  } catch (error) {
26260
+ orphanReason = `old API Gateway Resource ${physicalId} could not be deleted: ${String(error)}`;
26250
26261
  this.logger.warn(`Failed to delete old API Gateway Resource ${physicalId} during replacement: ${String(error)}. The old resource may be orphaned and require manual cleanup.`);
26251
26262
  }
26252
- return {
26263
+ const base = {
26253
26264
  physicalId: createResult.physicalId,
26254
26265
  wasReplaced: true,
26255
26266
  attributes: createResult.attributes ?? {}
26256
26267
  };
26268
+ return orphanReason !== void 0 ? {
26269
+ ...base,
26270
+ outcome: "partial",
26271
+ reason: orphanReason
26272
+ } : base;
26257
26273
  }
26258
26274
  return {
26259
26275
  physicalId,
@@ -39034,10 +39050,17 @@ function hasMfaConfigProps(properties) {
39034
39050
  * - `WebAuthnRelyingPartyID`/`WebAuthnUserVerification` -> `WebAuthnConfiguration`
39035
39051
  *
39036
39052
  * `MfaConfiguration` (ON/OFF/OPTIONAL) MUST be threaded into this request:
39037
- * `SetUserPoolMfaConfig` is a full-replace of the pool's MFA state, and an
39038
- * omitted `MfaConfiguration` defaults to OFF on the wire which resets the
39039
- * pool to MFA-disabled and makes AWS reject (or silently drop) the per-factor
39040
- * sub-blocks we are trying to enable. Use the template's `MfaConfiguration`
39053
+ * `SetUserPoolMfaConfig` is a full-replace of the pool's MFA state, and AWS
39054
+ * reads an omitted `MfaConfiguration` as OFF. That splits into two MUTUALLY
39055
+ * EXCLUSIVE arms both MEASURED against THIS API, not read off
39056
+ * `UpdateUserPool`'s blanket reset sentence, which holds only field by field:
39057
+ *
39058
+ * - WITH a per-factor sub-block in the request, AWS REJECTS the call: nothing
39059
+ * is reset, and the deploy FAILS.
39060
+ * - WITHOUT one, AWS accepts the call and the pool is silently reset to
39061
+ * MFA-disabled.
39062
+ *
39063
+ * Use the template's `MfaConfiguration`
39041
39064
  * when present; when the template omitted it, default by whether this call
39042
39065
  * enables an MFA FACTOR — `OPTIONAL` if it does, `OFF` (CloudFormation's own
39043
39066
  * default) if it does not. The body comment on that decision explains why both
@@ -39075,11 +39098,11 @@ function buildMfaConfigRequest(physicalId, properties, logger, declaredMfaConfig
39075
39098
  request.MfaConfiguration = declaredMfaConfiguration || (enablesMfaFactor ? "OPTIONAL" : "OFF");
39076
39099
  if (enabledMfasBlank) logger?.warn(`UserPool ${physicalId}: EnabledMfas is an empty string, which declares no MFA factor and is treated as absent — a mangled Fn::Join or an empty String parameter collapses to this shape. Nothing is enabled from it and MfaConfiguration is ${request.MfaConfiguration}; declare the factor names as a list (e.g. ["${MFA_FACTOR_SOFTWARE_TOKEN}"]) if a factor was intended.`);
39077
39100
  const anyFactorBlock = request.SmsMfaConfiguration !== void 0 || request.SoftwareTokenMfaConfiguration !== void 0 || request.EmailMfaConfiguration !== void 0;
39078
- if (request.MfaConfiguration === "OFF" && anyFactorBlock) logger?.warn(`UserPool ${physicalId}: MfaConfiguration is pinned to OFF while an MFA factor block is configured. The factor configuration is still sent, but the pool deploys with MFA DISABLED. Remove MfaConfiguration (or set it to ON / OPTIONAL) to enable the declared factor.`);
39101
+ if (request.MfaConfiguration === "OFF" && anyFactorBlock) logger?.warn(`UserPool ${physicalId}: MfaConfiguration is pinned to OFF while an MFA factor block is configured. AWS REJECTS that combination ("Invalid MFA configuration given, can't turn off MFA and configure an MFA together"), so this deploy FAILS rather than producing a pool with MFA disabled. Remove MfaConfiguration (or set it to ON / OPTIONAL) to enable the declared factor.`);
39079
39102
  const dropped = unrecognizedFactors.map((f) => JSON.stringify(f) ?? String(f));
39080
39103
  if (enabledMfasMalformed) dropped.push(`${JSON.stringify(properties["EnabledMfas"])} (not a list)`);
39081
39104
  if (dropped.length > 0) {
39082
- const consequence = request.MfaConfiguration === "OFF" ? `MfaConfiguration is OFF, so this pool deploys with MFA DISABLED` : anyFactorBlock ? `MfaConfiguration is ${request.MfaConfiguration} and another factor block IS sent, so these entries are silently ignored rather than failing the call on their own` : `MfaConfiguration is ${request.MfaConfiguration} with no factor enabled, so AWS rejects this call rather than deploying the pool with MFA disabled`;
39105
+ const consequence = request.MfaConfiguration === "OFF" && anyFactorBlock ? "MfaConfiguration is OFF while another factor block IS sent, so AWS REJECTS this call outright and the deploy fails" : request.MfaConfiguration === "OFF" ? `MfaConfiguration is OFF, so this pool deploys with MFA DISABLED` : anyFactorBlock ? `MfaConfiguration is ${request.MfaConfiguration} and another factor block IS sent, so these entries are silently ignored rather than failing the call on their own` : `MfaConfiguration is ${request.MfaConfiguration} with no factor enabled, so AWS rejects this call rather than deploying the pool with MFA disabled`;
39083
39106
  logger?.warn(`UserPool ${physicalId}: EnabledMfas entries ${dropped.join(", ")} do not map to an MFA factor block (known: ${MFA_FACTOR_SMS}, ${MFA_FACTOR_SOFTWARE_TOKEN}, ${MFA_FACTOR_EMAIL_OTP}); no factor is enabled from them. ${consequence}.`);
39084
39107
  }
39085
39108
  return request;
@@ -39243,9 +39266,52 @@ var CognitoUserPoolProvider = class {
39243
39266
  /**
39244
39267
  * Build the SDK `Policies` input from the CFn `Policies` blob. Both
39245
39268
  * sub-keys must be forwarded: `SignInPolicy` (passwordless first-auth
39246
- * factors) was silently dropped before issue #1380, and UpdateUserPool
39247
- * resets omitted attributes to their defaults, so leaving it out also
39248
- * wipes an existing sign-in policy on every update.
39269
+ * factors) was silently dropped before issue #1380, so a template that
39270
+ * declared -- or later CHANGED -- the allowed first-auth factors never
39271
+ * reached AWS at all.
39272
+ *
39273
+ * **Forwarding is what APPLIES a declared value; it is NOT what keeps an
39274
+ * existing one alive.** The original rationale here was AWS's blanket "if
39275
+ * you don't provide a value for an attribute, Amazon Cognito sets it to its
39276
+ * default value", i.e. that omitting `SignInPolicy` would WIPE it. MEASURED
39277
+ * us-east-1 2026-08-19 (issue #1968), on a pool created with
39278
+ * `SignInPolicy.AllowedFirstAuthFactors = [PASSWORD, EMAIL_OTP]` on the
39279
+ * default `ESSENTIALS` tier -- no explicit `UserPoolTier` was needed for the
39280
+ * field to be settable:
39281
+ *
39282
+ * - `UpdateUserPool` sending `Policies` with ONLY `PasswordPolicy` left
39283
+ * `SignInPolicy` at `[PASSWORD, EMAIL_OTP]`. Control: that same call's
39284
+ * `--auto-verified-attributes email` applied, so the update really ran.
39285
+ * - `UpdateUserPool` omitting `Policies` entirely left BOTH sub-keys intact
39286
+ * (a non-default `RequireSymbols: false` survived too), while
39287
+ * `AutoVerifiedAttributes` reset from `[email]` to none in that very call
39288
+ * -- the control proving the omission was processed, not ignored.
39289
+ * - An explicit `SignInPolicy` write landed in both directions
39290
+ * (`[PASSWORD]`, then back to `[PASSWORD, EMAIL_OTP]`), so the field is
39291
+ * reachable and writable here rather than silently dropped.
39292
+ * - The MIRROR image holds too, so the ledger entry is measured rather than
39293
+ * generalised from one sub-key: a container carrying ONLY `SignInPolicy`
39294
+ * updated it to `[PASSWORD, WEB_AUTHN]` while leaving a non-default
39295
+ * `PasswordPolicy` (`MinimumLength: 12`, `RequireSymbols: false`) intact,
39296
+ * with the same `--auto-verified-attributes` control.
39297
+ *
39298
+ * So the forwarding stays -- it is the only way a CHANGED sign-in policy is
39299
+ * applied -- but on the measured reason, not the blanket sentence. Do not
39300
+ * generalise that sentence to another field from this site: see
39301
+ * `readLiveMfaConfiguration`'s docstring, which carries the one ledger of
39302
+ * which fields were measured to reset and which were not.
39303
+ *
39304
+ * **Consequence of that measurement, NOT fixed here: there is no removal
39305
+ * path.** This builder forwards only what the template DECLARES, so deleting
39306
+ * `SignInPolicy` from a template sends nothing for it -- and nothing sent
39307
+ * means nothing changed. A user tightening their auth by removing a
39308
+ * passwordless first-auth factor therefore leaves it live on the pool
39309
+ * indefinitely, and `readCurrentState` keeps reporting a drift that
39310
+ * `cdkd drift --revert` cannot clear, since the revert routes through this
39311
+ * same builder. Clearing it needs an explicit reset value on the wire, which
39312
+ * is a behavior change with its own integ and is out of this lane's scope.
39313
+ * Whether CloudFormation removes it on the same template edit is UNMEASURED
39314
+ * -- do not assume parity in either direction.
39249
39315
  */
39250
39316
  toSdkUserPoolPolicies(policies) {
39251
39317
  const result = {};
@@ -39402,10 +39468,37 @@ var CognitoUserPoolProvider = class {
39402
39468
  * `--auto-verified-attributes` change in the same request applied) and that
39403
39469
  * the field is writable here rather than ignored (an explicit
39404
39470
  * `--mfa-configuration OFF` did reset it). So AWS's blanket "unspecified
39405
- * parameters are set to their default value" holds field by field —
39406
- * `AutoVerifiedAttributes` DOES reset, `MfaConfiguration` does not — and
39471
+ * parameters are set to their default value" holds field by field — and
39407
39472
  * there is no MFA-OFF window to design around.
39408
39473
  *
39474
+ * **This paragraph is the single ledger of that field-by-field result; a
39475
+ * per-field verdict belongs at that field's own site, pointing here.** Every
39476
+ * line below was taken with a control proving the omitting call really ran.
39477
+ *
39478
+ * `UpdateUserPool`, on omission:
39479
+ *
39480
+ * - `AutoVerifiedAttributes` — DOES reset when omitted (2026-08-18).
39481
+ * - `MfaConfiguration` — does NOT reset when omitted (2026-08-18, above).
39482
+ * - `Policies` — neither sub-key resets, measured in BOTH directions
39483
+ * (2026-08-19, issue #1968; details at `toSdkUserPoolPolicies`): a
39484
+ * container sent without `SignInPolicy` left `SignInPolicy` intact, a
39485
+ * container sent without `PasswordPolicy` left `PasswordPolicy` intact,
39486
+ * and omitting the container outright left both. Forwarding is still
39487
+ * REQUIRED to APPLY a changed sub-key — preservation is not application —
39488
+ * and no omission can express a REMOVAL.
39489
+ *
39490
+ * `SetUserPoolMfaConfig` — a DIFFERENT API with a DIFFERENT rule, kept in
39491
+ * its own section so neither list is read as evidence for the other:
39492
+ *
39493
+ * - `MfaConfiguration` — an omitted value is read as OFF. With a factor
39494
+ * sub-block in the request AWS REJECTS the call; with none it accepts and
39495
+ * resets the pool to MFA-disabled. An EXPLICIT OFF beside a block is
39496
+ * rejected the same way (2026-08-19, issue #1968; the transcript lives at
39497
+ * the pinned-OFF warning in `buildMfaConfigRequest`).
39498
+ *
39499
+ * Nothing here licenses a claim about a field on neither list, or about one
39500
+ * API from the other's section. Measure it.
39501
+ *
39409
39502
  * The earlier ordering was reached from that doc sentence alone and is kept
39410
39503
  * only because it is strictly safer and already tested: reading first cannot
39411
39504
  * report a value this same call clobbered, whatever AWS does later. Do not
@@ -56621,6 +56714,26 @@ var WaitConditionHandleProvider = class {
56621
56714
  //#endregion
56622
56715
  //#region src/provisioning/providers/acm-certificate-provider.ts
56623
56716
  /**
56717
+ * True for ACM's refusal to delete a certificate a consumer still references
56718
+ * (issue [#1922](https://github.com/go-to-k/cdkd/issues/1922)).
56719
+ *
56720
+ * Matched by error NAME first, with a message fallback: the SDK raises
56721
+ * `ResourceInUseException`, but a wrapped / re-thrown error can lose the class
56722
+ * while keeping the text. Deliberately narrow — this only picks the
56723
+ * remediation wording and the reason phrasing, and every other failure still
56724
+ * reaches the same `'partial'` outcome, so a miss degrades to a less specific
56725
+ * message rather than to the pre-#1922 silence.
56726
+ */
56727
+ function isCertificateInUseError(error) {
56728
+ let current = error;
56729
+ for (let depth = 0; depth < 5 && current instanceof Error; depth++) {
56730
+ if (current.name === "ResourceInUseException") return true;
56731
+ current = current.cause;
56732
+ }
56733
+ const message = error instanceof Error ? error.message : String(error);
56734
+ return /ResourceInUseException|in use/i.test(message);
56735
+ }
56736
+ /**
56624
56737
  * AWS ACM Certificate Provider
56625
56738
  *
56626
56739
  * Implements `AWS::CertificateManager::Certificate` using the ACM SDK.
@@ -56718,18 +56831,28 @@ var ACMCertificateProvider = class {
56718
56831
  if (changedImmutable) {
56719
56832
  this.logger.debug(`${changedImmutable} changed, replacing ACM certificate: ${physicalId}`);
56720
56833
  const createResult = await this.create(logicalId, resourceType, properties);
56834
+ let orphanReason;
56721
56835
  try {
56722
56836
  const deleteResult = await this.delete(logicalId, physicalId, resourceType, previousProperties);
56723
- if (deleteResult?.outcome === "skipped") this.logger.warn(`Skipped deleting old ACM certificate ${physicalId} during replacement: ${deleteResult.reason}. The old certificate may be orphaned and require manual cleanup.`);
56837
+ if (deleteResult?.outcome === "skipped") {
56838
+ orphanReason = `old certificate ${physicalId} was not deleted: ${deleteResult.reason}`;
56839
+ this.logger.warn(`Skipped deleting old ACM certificate ${physicalId} during replacement: ${deleteResult.reason}. The old certificate may be orphaned and require manual cleanup.`);
56840
+ }
56724
56841
  } catch (error) {
56725
- this.logger.warn(`Failed to delete old ACM certificate ${physicalId} during replacement: ${String(error)}. The old certificate may be orphaned and require manual cleanup.`);
56842
+ const inUse = isCertificateInUseError(error);
56843
+ orphanReason = inUse ? `old certificate ${physicalId} is still in use by another resource and was not deleted` : `old certificate ${physicalId} could not be deleted: ${String(error)}`;
56844
+ this.logger.warn(`Failed to delete old ACM certificate ${physicalId} during replacement: ${String(error)}. ` + (inUse ? "This usually means a consumer (e.g. a CloudFront distribution) still references it; it can be deleted once DescribeCertificate.InUseBy is empty. " : "") + `The old certificate may be orphaned and require manual cleanup.`);
56726
56845
  }
56727
- const result = {
56846
+ const base = {
56728
56847
  physicalId: createResult.physicalId,
56729
- wasReplaced: true
56848
+ wasReplaced: true,
56849
+ ...createResult.attributes ? { attributes: createResult.attributes } : {}
56730
56850
  };
56731
- if (createResult.attributes) result.attributes = createResult.attributes;
56732
- return result;
56851
+ return orphanReason !== void 0 ? {
56852
+ ...base,
56853
+ outcome: "partial",
56854
+ reason: orphanReason
56855
+ } : base;
56733
56856
  }
56734
56857
  try {
56735
56858
  const newCt = properties["CertificateTransparencyLoggingPreference"];
@@ -57590,9 +57713,11 @@ async function deployCommand(stacks, options) {
57590
57713
  logger.info(`\n${bold("Deployment Summary:")}`);
57591
57714
  logger.info(` Stack: ${bold(cyan(deployResult.stackName))}`);
57592
57715
  logger.info(` Created: ${deployResult.created > 0 ? green(deployResult.created) : gray(deployResult.created)}`);
57593
- logger.info(` Updated: ${deployResult.updated > 0 ? yellow(deployResult.updated) : gray(deployResult.updated)}`);
57716
+ const updatedTotal = deployResult.updated + deployResult.updatePartial;
57717
+ logger.info(` Updated: ${updatedTotal > 0 ? yellow(updatedTotal) : gray(updatedTotal)}`);
57594
57718
  logger.info(` Deleted: ${deployResult.deleted > 0 ? red(deployResult.deleted) : gray(deployResult.deleted)}`);
57595
57719
  if (deployResult.deleteSkipped > 0) logger.info(` Skipped (not deleted): ${yellow(deployResult.deleteSkipped)}`);
57720
+ if (deployResult.updatePartial > 0) logger.info(` of which left an orphaned predecessor: ${yellow(deployResult.updatePartial)}`);
57596
57721
  logger.info(` Unchanged: ${gray(deployResult.unchanged)}`);
57597
57722
  logger.info(` Duration: ${cyan((deployResult.durationMs / 1e3).toFixed(2) + "s")}`);
57598
57723
  if (deployResult.attributeFallbackCount > 0) logger.warn(`${deployResult.attributeFallbackCount} attribute resolution(s) fell back to the physical ID (potentially wrong values); re-run with --strict-getatt to fail on these`);
@@ -57604,9 +57729,9 @@ async function deployCommand(stacks, options) {
57604
57729
  else logger.info(`\n${green("✓")} ${bold("Deployment completed successfully")}`);
57605
57730
  recordRunSucceeded(eventRecorder, stackInfo.stackName, {
57606
57731
  created: deployResult.created,
57607
- updated: deployResult.updated,
57732
+ updated: deployResult.updated + deployResult.updatePartial,
57608
57733
  deleted: deployResult.deleted,
57609
- ...deployResult.deleteSkipped > 0 && { skipped: deployResult.deleteSkipped }
57734
+ ...deployResult.deleteSkipped + deployResult.updatePartial > 0 && { skipped: deployResult.deleteSkipped + deployResult.updatePartial }
57610
57735
  }, deployResult.durationMs);
57611
57736
  } catch (deployError) {
57612
57737
  if (deployError instanceof DeployCancelledError) return;
@@ -62214,7 +62339,9 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
62214
62339
  try {
62215
62340
  const updateResult = await withRetry(() => provider.update(outcome.logicalId, stateResource.physicalId, outcome.resourceType, newProperties, outcome.awsProperties, { desiredFromAwsReadback: true }), outcome.logicalId, { logger: { debug: (msg) => logger.debug(maskSecretsInText(msg, secrets)) } });
62216
62341
  totalSucceeded++;
62217
- logger.info(` ✓ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted.`);
62342
+ const revertPartial = updatePartialReason(updateResult);
62343
+ if (revertPartial !== void 0) logger.warn(` ✓ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted, ${maskSecretsInText(updatePartialMessage(revertPartial), secrets)}`);
62344
+ else logger.info(` ✓ ${report.stackName}/${outcome.logicalId} (${outcome.resourceType}): reverted.`);
62218
62345
  try {
62219
62346
  if (updateResult?.effectiveProperties) {
62220
62347
  const delta = collectNarrowedTopLevelKeys(newProperties, updateResult.effectiveProperties);
@@ -81246,7 +81373,7 @@ function createMigrateCommand() {
81246
81373
  */
81247
81374
  function buildProgram() {
81248
81375
  const program = new Command();
81249
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.283.36");
81376
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.284.1");
81250
81377
  program.hook("preAction", (_thisCommand, actionCommand) => {
81251
81378
  const { profile } = actionCommand.optsWithGlobals();
81252
81379
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;