@go-to-k/cdkd 0.290.24 → 0.290.26

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.
@@ -1,8 +1,8 @@
1
- import { $ as secretBearing, $n as scrubResourceRecord, $r as Synthesizer, $t as s3BucketArn, A as extractDeploymentEventError, Ai as setAwsClients, An as repairMalformedResourcesForReadOnly, Ar as isCrossRegionRedirect, At as isImpossibleEmptyStoredAttribute, B as toCfnInteger, Bi as LocalStartServiceError, Bn as TEMPLATE_SOURCED_RULES, Br as describeDockerExecFailure, Bt as coerceCfnBoolean, C as buildFinalSnapshotIdentifier, Ci as AssemblyReader, Cn as refuseMalformedOutputs, Cr as loadPublishableAssetManifest, Ct as isTerminationProtectionPropagationError, D as refusesFinalSnapshot, Di as AwsClients, Dn as refuseMalformedState, Dr as assertAssetBucketRegion, Dt as cfnRefValueFromPhysicalId, E as isFinalSnapshotError, Ei as resolveBucketRegion, En as refuseMalformedResourcesForDestroy, Er as BOOTSTRAP_MARKER_PREFIX, Et as carriesFetchableDynamicReference, F as isStatefulRecreateTargetSync, Fn as DagBuilder, Fr as buildDenyExternalAccessPolicy, Ft as isSettledInstanceState, G as gray, Gi as ProvisioningError, Gn as identityKeyFor, Gr as partitionSensitiveEnv, Gt as readConfigString, H as formatResourceLine, Hi as MalformedProducerRecordRefusalError, Hn as createSecretMasker, Hr as dockerSpawnEnvWithSensitive, Ht as configBooleanRefusal, I as renderStatefulReason, Ii as DeployCancelledError, In as TemplateParser, Ir as describeAwsFailure, It as WAFv2WebACLProvider, J as yellow, Ji as StackHasActiveImportsError, Jn as maskSecretsInError, Jr as runDockerStreaming, Jt as requireConfigObject, K as green, Ki as ResourceTimeoutError, Kn as isSingleDynamicReferenceToken, Kr as redactDockerArgvValues, Kt as replayWarn, L as WARM_THROUGHPUT_MEMBERS, Li as DynamicReferenceRegionAmbiguousError, Ln as STATE_SOURCED_BASELINE_RULES, Lr as safeStringify, Lt as normalizeAwsTagsToCfn, M as planOrphanAdoption, Mi as CdkdError, Mn as findSilentDropProperties, Mr as readBootstrapMarkerBody, Mt as isUnboundTemplateParameter, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as describeTypeWithThrottleRetry, Nr as validateAssetBucketName, Nt as parameterTypeMayLoseSecretIdentity, O as unsupportedFinalSnapshotError, Oi as getAwsClients, On as repairMalformedOutputsForReadOnly, Or as ensureAssetStorage, Ot as coerceParameterTypedValue, P as isStatefulRecreateTargetForReplace, Pi as CrossAccountSecretRefusalError, Pn as withRetry, Pr as validateContainerRepoName, Pt as refStateLookupFromResource, Q as isExportAliasCollision, Qn as redactSecretsForState, Qr as getDockerImageBySourceHash, Qt as withSharedDrainBudget, R as coerceWarmThroughput$1, Ri as IntrinsicResolutionRefusalError, Rn as STATE_SOURCED_CROSS_GENERATION_RULES, Rr as buildDockerImage, Rt as resolveExplicitPhysicalId, S as PRE_DELETE_SNAPSHOT_TYPES, Si as derivePartitionAndUrlSuffix, Sn as refuseMalformedNestedChildOutputs, Sr as createAssetRedirectResolver, St as disableInstanceApiTermination, T as createPreDeleteFinalSnapshot, Tn as refuseMalformedResourcePropertiesForOrphan, Tr as AssetModeResolver, Tt as carriesDynamicReference, U as bold, Ui as NestedStackChildDirectDestroyError, Un as dynamicReferenceTokens, Ur as formatDockerLoginError, Ut as configIntegerRefusal, V as toFiniteNumber, Vn as carriesSecretMask, Vr as describeDockerFailure, Vt as coerceCfnInteger, W as cyan, Wi as PartialFailureError, Wn as errorCauseChain, Wr as getDockerCmd, Wt as configStringRefusal, X as collectPublishedOutputNames, Xn as recordMaskOnlyValue, Xr as stripControlChars, Xt as classifyReplaySecretRegion, Y as collectDeclaredOutputNames, Yi as StackTerminationProtectionError, Yn as maskSecretsInText, Yr as escapeRegExp, Yt as requireConfigString, Z as exportAliasCollisionScrubWarning, Zn as recoverMaskedOutput, Zr as AssetManifestLoader, Zt as producerRegionsFromState, _ as isSecretBearingReferenceString, _i as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, _n as malformedRenderedContainersWarning, _r as shouldRetainResource, _t as isWaitAbandonedError, a as DeploymentEventsStore, aa as isTransientServerError, ai as resolveCaptureObservedState, an as applyCallerIdentityCredentials, ar as forceQuitRecoveryClause, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bi as PARTITION_TABLE, bn as malformedStateRefusalMessage, br as WorkGraph, bt as deleteIndeterminateGuards, c as replayFailedOperations, ca as retryClassificationText, ci as resolveStateBucketWithDefaultAndSource, cn as INTRINSIC_KEYS, cr as CUSTOM_RESOURCE_RESPONSE_PREFIX, ct as createMaskedRetryLogger, d as updatePartialReason, di as warnDeprecatedNoPrefixCliFlag, dn as hasReadableResources, dr as hasReadableExportSet, dt as beginCommandInterruptScope, ea as normalizeAwsError, ei as synthesisStatusMessage, en as s3BucketDomainName, er as LockManager, et as secretBearingStateKeyWarning, f as withResourceDeadline, fi as CFN_TEMPLATE_BODY_LIMIT, fn as malformedExportNamesWarning, fr as importableOutputKeys, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as uploadCfnTemplate, gn as malformedOutputsWarning, gr as orphansCarriedFrom, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as findLargeInlineResources, hn as malformedOutputsRefusalMessage, hr as orphansAfterRollback, ht as startInterruptWatch, i as DeploymentEventsReader, ia as isThrottlingError, ii as resolveAutoAssetStorage, in as applyRoleArnIfSet, ir as buildLockContentionMessage, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, jn as findActionableSilentDrops, jr as parseBootstrapMarker, jt as isNamelessDynamicReferenceError, k as makeCanonicalizePropertiesFn, ki as resetAwsClients, kn as repairMalformedResourcePropertiesForReadOnly, kr as getBootstrapMarkerKey, kt as getAccountInfo, l as replayRollback, li as resolveUseCdkBootstrapAssets, ln as STATE_RESOURCES_MALFORMED, lr as DEFAULT_STATE_PREFIX, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as MIGRATE_TMP_PREFIX, mn as malformedLocalOutputsWarning, mr as isReadableBag, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as isMarkedNonRetryable, ni as getLegacyStateBucketName, nn as s3BucketRegionalDomainName, nr as rebuildClientForBucketRegion, nt as getCurrentResourceSecrets, o as planFailedOps, oa as markNonRetryable, oi as resolveSkipPrefix, on as callerEnvCredentials, or as formatLockExpiry, ot as ProviderRegistry, p as maskingRetryLogger, pi as CFN_TEMPLATE_URL_LIMIT, pn as malformedExportSourceWarning, pr as importableOutputs, pt as interruptWatchListenerCount, q as red, qi as ResourceUpdateNotSupportedError, qn as markSameGenerationBag, qr as runDockerForeground, qt as requireConfigArray, r as DeployEngine, ra as isRetryableTransientError, ri as resolveApp, rn as s3BucketWebsiteUrl, rr as buildForceUnlockCommand, rt as IAMRoleProvider, s as planRollback, sa as markRedactedCause, si as resolveStateBucketWithDefault, sn as DiffCalculator, sr as shellQuote, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as withErrorHandling, ti as getDefaultStateBucketName, tn as s3BucketDualStackDomainName, tr as S3StateBackend, tt as secretSafeKeyDisplay, u as updatePartialMessage, ui as stateBucketExistenceConfirmed, un as hasReadableOutputs, ur as exportNamesCarriedFrom, ut as maskerOrIdentity, v as keptWholeReasonText, vi as decodeListingKey, vn as malformedResourcePropertiesWarning, vr as AssetPublisher, vt as slowCcOperationTimeoutMs, w as ccRoutedFinalSnapshotError, wi as processStackMessages, wn as refuseMalformedOutputsForDestroy, wr as rewriteTemplateAssetReferences, wt as IntrinsicFunctionResolver, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xi as canonicalizeRegion, xn as producerRecordKey, xr as buildAssetRedirectMap, xt as deleteSkipReason, y as mergeNoChangeOutputs, yi as expectedOwnerParam, yn as malformedResourcesWarning, yr as stringifyValue, yt as UNSPECIFIED_SKIP_REASON, z as isWarmThroughputDecrease, zi as LocalInvokeBuildError$1, zn as STATE_SOURCED_READBACK_RULES, zr as describeDockerCapturedOutput, zt as assertRegionMatch } from "./deploy-engine-BcNBidfy.js";
1
+ import { $ as secretBearing, $n as recordMaskOnlyValue, $r as getDockerImageBySourceHash, $t as s3BucketArn, A as extractDeploymentEventError, Ai as resetAwsClients, An as refuseMalformedState, Ar as assertAssetBucketRegion, At as isImpossibleEmptyStoredAttribute, B as toCfnInteger, Bi as LocalInvokeBuildError$1, Bn as STATE_SOURCED_BASELINE_RULES, Br as describeDockerCapturedOutput, Bt as coerceCfnBoolean, C as buildFinalSnapshotIdentifier, Ci as derivePartitionAndUrlSuffix, Cn as producerRecordKey, Cr as WorkGraph, Ct as isTerminationProtectionPropagationError, D as refusesFinalSnapshot, Di as resolveBucketRegion, Dn as refuseMalformedOutputsForDestroy, Dr as rewriteTemplateAssetReferences, Dt as cfnRefValueFromPhysicalId, E as isFinalSnapshotError, En as refuseMalformedOutputs, Er as loadPublishableAssetManifest, Et as carriesFetchableDynamicReference, F as isStatefulRecreateTargetSync, Fi as CrossAccountSecretRefusalError, Fn as findSilentDropProperties, Fr as readBootstrapMarkerBody, Ft as isSettledInstanceState, G as gray, Gi as PartialFailureError, Gn as createSecretMasker, Gr as getDockerCmd, Gt as readConfigString, H as formatResourceLine, Hn as STATE_SOURCED_READBACK_RULES, Hr as describeDockerFailure, Ht as configBooleanRefusal, I as renderStatefulReason, In as describeTypeWithThrottleRetry, Ir as validateAssetBucketName, It as WAFv2WebACLProvider, J as yellow, Ji as ResourceUpdateNotSupportedError, Jn as identityKeyFor, Jr as runDockerForeground, Jt as requireConfigObject, K as green, Ki as ProvisioningError, Kn as dynamicReferenceTokens, Kr as partitionSensitiveEnv, Kt as replayWarn, L as WARM_THROUGHPUT_MEMBERS, Li as DeployCancelledError, Ln as withRetry, Lr as validateContainerRepoName, Lt as normalizeAwsTagsToCfn, M as planOrphanAdoption, Mn as repairMalformedResourcePropertiesForReadOnly, Mr as getBootstrapMarkerKey, Mt as isUnboundTemplateParameter, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Ni as CdkdError, Nn as repairMalformedResourcesForReadOnly, Nr as isCrossRegionRedirect, Nt as parameterTypeMayLoseSecretIdentity, O as unsupportedFinalSnapshotError, Oi as AwsClients, On as refuseMalformedResourcePropertiesForOrphan, Or as AssetModeResolver, Ot as coerceParameterTypedValue, P as isStatefulRecreateTargetForReplace, Pn as findActionableSilentDrops, Pr as parseBootstrapMarker, Pt as refStateLookupFromResource, Q as isExportAliasCollision, Qn as maskSecretsInText, Qr as AssetManifestLoader, Qt as withSharedDrainBudget, R as coerceWarmThroughput$1, Ri as DynamicReferenceRegionAmbiguousError, Rn as DagBuilder, Rr as buildDenyExternalAccessPolicy, Rt as resolveExplicitPhysicalId, S as PRE_DELETE_SNAPSHOT_TYPES, Si as canonicalizeRegion, Sn as malformedStateRefusalMessage, Sr as stringifyValue, St as disableInstanceApiTermination, T as createPreDeleteFinalSnapshot, Ti as processStackMessages, Tn as refuseMalformedNestedChildOutputs, Tr as createAssetRedirectResolver, Tt as carriesDynamicReference, U as bold, Ui as MalformedProducerRecordRefusalError, Un as TEMPLATE_SOURCED_RULES, Ur as dockerSpawnEnvWithSensitive, Ut as configIntegerRefusal, V as toFiniteNumber, Vi as LocalStartServiceError, Vn as STATE_SOURCED_CROSS_GENERATION_RULES, Vr as describeDockerExecFailure, Vt as coerceCfnInteger, W as cyan, Wi as NestedStackChildDirectDestroyError, Wn as carriesSecretMask, Wr as formatDockerLoginError, Wt as configStringRefusal, X as collectPublishedOutputNames, Xi as StackTerminationProtectionError, Xn as markSameGenerationBag, Xr as escapeRegExp, Xt as classifyReplaySecretRegion, Y as collectDeclaredOutputNames, Yi as StackHasActiveImportsError, Yn as isSingleDynamicReferenceToken, Yr as runDockerStreaming, Yt as requireConfigString, Z as exportAliasCollisionScrubWarning, Zn as maskSecretsInError, Zr as stripControlChars, Zt as producerRegionsFromState, _ as isSecretBearingReferenceString, _i as uploadCfnTemplate, _n as malformedOutputsRefusalMessage, _r as isReadableBag, _t as isWaitAbandonedError, a as DeploymentEventsStore, aa as isThrottlingError, ai as resolveAutoAssetStorage, an as AWS_CREDENTIAL_ENV_KEYS, ar as rebuildClientForBucketRegion, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bi as expectedOwnerParam, bn as malformedResourcePropertiesWarning, br as shouldRetainResource, bt as deleteIndeterminateGuards, c as replayFailedOperations, ca as markRedactedCause, ci as resolveStateBucketWithDefault, cn as envCredentialsAreAssumedRole, cr as forceQuitRecoveryClause, ct as createMaskedRetryLogger, d as updatePartialReason, da as safeStringify, di as stateBucketExistenceConfirmed, dn as STATE_RESOURCES_MALFORMED, dr as CUSTOM_RESOURCE_RESPONSE_PREFIX, dt as beginCommandInterruptScope, ei as Synthesizer, en as s3BucketDomainName, er as recoverMaskedOutput, et as secretBearingStateKeyWarning, f as withResourceDeadline, fi as warnDeprecatedNoPrefixCliFlag, fn as hasReadableOutputs, fr as DEFAULT_STATE_PREFIX, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as findLargeInlineResources, gn as malformedLocalOutputsWarning, gr as importableOutputs, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as MIGRATE_TMP_PREFIX, hn as malformedExportSourceWarning, hr as importableOutputKeys, ht as startInterruptWatch, i as DeploymentEventsReader, ia as isRetryableTransientError, ii as resolveApp, in as applyRoleArnIfSet, ir as S3StateBackend, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, ji as setAwsClients, jn as repairMalformedOutputsForReadOnly, jr as ensureAssetStorage, jt as isNamelessDynamicReferenceError, k as makeCanonicalizePropertiesFn, ki as getAwsClients, kn as refuseMalformedResourcesForDestroy, kr as BOOTSTRAP_MARKER_PREFIX, kt as getAccountInfo, l as replayRollback, la as retryClassificationText, li as resolveStateBucketWithDefaultAndSource, ln as DiffCalculator, lr as formatLockExpiry, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as CFN_TEMPLATE_URL_LIMIT, mn as malformedExportNamesWarning, mr as hasReadableExportSet, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as withErrorHandling, ni as getDefaultStateBucketName, nn as s3BucketRegionalDomainName, nr as scrubResourceRecord, nt as getCurrentResourceSecrets, o as planFailedOps, oa as isTransientServerError, oi as resolveCaptureObservedState, on as applyCallerIdentityCredentials, or as buildForceUnlockCommand, ot as ProviderRegistry, p as maskingRetryLogger, pi as CFN_TEMPLATE_BODY_LIMIT, pn as hasReadableResources, pr as exportNamesCarriedFrom, pt as interruptWatchListenerCount, q as red, qi as ResourceTimeoutError, qn as errorCauseChain, qr as redactDockerArgvValues, qt as requireConfigArray, r as DeployEngine, ra as isMarkedNonRetryable, ri as getLegacyStateBucketName, rn as s3BucketWebsiteUrl, rr as LockManager, rt as IAMRoleProvider, s as planRollback, sa as markNonRetryable, si as resolveSkipPrefix, sn as callerEnvCredentials, sr as buildLockContentionMessage, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ta as normalizeAwsError, ti as synthesisStatusMessage, tn as s3BucketDualStackDomainName, tr as redactSecretsForState, tt as secretSafeKeyDisplay, u as updatePartialMessage, ua as describeAwsFailure, ui as resolveUseCdkBootstrapAssets, un as INTRINSIC_KEYS, ur as shellQuote, ut as maskerOrIdentity, v as keptWholeReasonText, vi as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, vn as malformedOutputsWarning, vr as orphansAfterRollback, vt as slowCcOperationTimeoutMs, w as ccRoutedFinalSnapshotError, wi as AssemblyReader, wn as refuseDivergentRecordRegionForDestroy, wr as buildAssetRedirectMap, wt as IntrinsicFunctionResolver, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xi as PARTITION_TABLE, xn as malformedResourcesWarning, xr as AssetPublisher, xt as deleteSkipReason, y as mergeNoChangeOutputs, yi as decodeListingKey, yn as malformedRenderedContainersWarning, yr as orphansCarriedFrom, yt as UNSPECIFIED_SKIP_REASON, z as isWarmThroughputDecrease, zi as IntrinsicResolutionRefusalError, zn as TemplateParser, zr as buildDockerImage, zt as assertRegionMatch } from "./deploy-engine-CbIc5Y73.js";
2
2
  import { C as displaySafe, S as displayIdent, _ as withSkipPrefix, b as STACK_REF_MAX_CODE_POINTS, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, w as truncateCodePoints, x as UNRENDERABLE } from "./logger-CkHVgzLx.js";
3
3
  import { n as awsClientDefaults } from "./aws-client-defaults-BoIxyCir.js";
4
- import { t as getCdkdVersion } from "./version-PsB8OjhM.js";
5
- import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-CHeJf5zw.js";
4
+ import { t as getCdkdVersion } from "./version-yD49aCCv.js";
5
+ import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-7KM2pV3Q.js";
6
6
  import { AsyncLocalStorage } from "node:async_hooks";
7
7
  import { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  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, NotFound, 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";
@@ -3132,7 +3132,11 @@ async function probeStatefulRecreateTargetsAsync(targets, clients, logger = getL
3132
3132
  } catch (e) {
3133
3133
  if (e instanceof ResourceNotFoundException$5) {
3134
3134
  let regionVerified = true;
3135
- const recordedRegion = foldRegion(clients.expectedRegion);
3135
+ if (clients.expectedRegionDiverged) {
3136
+ regionVerified = false;
3137
+ logger.warn(`--recreate-via-cc-api / --recreate-via-sdk-provider: CloudWatch Logs reported ${target.logicalId} (log group ${target.physicalId}) missing, but this stack's state record names a region that is not the one its S3 key holds, so cdkd cannot attribute that not-found to any region — run 'cdkd state show' with --verbose to see what the record claims, and repair it. Re-run with --force-stateful-recreation if the log group really is disposable (that flag clears the data guard for every target in the run). Until then it is treated as NOT provably empty.`);
3138
+ }
3139
+ const recordedRegion = clients.expectedRegionDiverged ? void 0 : foldRegion(clients.expectedRegion);
3136
3140
  if (recordedRegion) {
3137
3141
  let clientRegion;
3138
3142
  try {
@@ -17831,6 +17835,20 @@ function hasUsableDeclaredCapacity(entry) {
17831
17835
  */
17832
17836
  const PROVISIONED_CAPACITY_MEMBERS = ["ReadCapacityUnits", "WriteCapacityUnits"];
17833
17837
  /**
17838
+ * The two `OnDemandThroughput` members, named once for the same reason
17839
+ * {@link PROVISIONED_CAPACITY_MEMBERS} is (issue
17840
+ * [#3265](https://github.com/go-to-k/cdkd/issues/3265)). CloudFormation's
17841
+ * `OnDemandThroughput` block carries the same two members at the TABLE level
17842
+ * and inside every `GlobalSecondaryIndexes[]` entry, and all FOUR send sites
17843
+ * read them through
17844
+ * {@link DynamoDBTableProvider.coerceOnDemandCeilingsForSend}.
17845
+ *
17846
+ * Both are `Long` in the SDK model and BOTH ARE OPTIONAL — the property that
17847
+ * makes this block's drop decision differ from its `ProvisionedThroughput`
17848
+ * sibling's. See {@link DynamoDBTableProvider.warnUnusableOnDemandCeiling}.
17849
+ */
17850
+ const ON_DEMAND_CEILING_MEMBERS = ["MaxReadRequestUnits", "MaxWriteRequestUnits"];
17851
+ /**
17834
17852
  * The capacity both TABLE-level forwarders substitute for a member the
17835
17853
  * template does not declare at all — `create()`'s `CreateTable` and the
17836
17854
  * BillingMode flip's `UpdateTable`. It is the pre-issue-#3147 `?? 5`, kept
@@ -18290,14 +18308,20 @@ function reverseMapSecondaryIndex(live, desired, bagInformative) {
18290
18308
  * in PR review round 6; the carve-out is gone because the divergence is.
18291
18309
  * - `ProvisionedThroughput` / `OnDemandThroughput` keep TRUTHINESS, which is
18292
18310
  * still what their write path does: `applyGsiUpdates` gates on
18293
- * `gsi.ProvisionedThroughput` alone, `OnDemandThroughput` is forwarded
18294
- * verbatim on every path, and the `ProvisionedThroughput` coercion issue
18295
- * [#3255](https://github.com/go-to-k/cdkd/issues/3255) added to ALL FOUR
18311
+ * `gsi.ProvisionedThroughput` / `gsi.OnDemandThroughput` alone, and NEITHER
18312
+ * block's Integer coercion — `ProvisionedThroughput`'s from issue
18313
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255), across ALL FOUR
18296
18314
  * per-index send sites (`create()`'s mapper plus `applyGsiUpdates`'
18297
- * adopted-repair, `Create` and same-name `Update` actions) never
18298
- * SUPPRESSES the block — it rewrites members inside a block that is sent
18299
- * either way, so "would cdkd send this block" is unchanged by it, even
18300
- * where both members were dropped and an empty block goes out. Widening
18315
+ * adopted-repair, `Create` and same-name `Update` actions);
18316
+ * `OnDemandThroughput`'s from issue
18317
+ * [#3265](https://github.com/go-to-k/cdkd/issues/3265), across its own four
18318
+ * (the two TABLE-level forwards plus `create()`'s mapper and
18319
+ * `applyGsiUpdates`' `Create` action) — ever SUPPRESSES the block: each
18320
+ * rewrites members inside a block that is sent either way, so "would cdkd
18321
+ * send this block" is unchanged by them, even where both members were
18322
+ * dropped and an empty block goes out. That is the reason
18323
+ * {@link DynamoDBTableProvider.coerceOnDemandCeilingsForSend} drops a
18324
+ * MEMBER and never the block. Widening
18301
18325
  * `isSendableWarmThroughput` over them would be wrong twice over — it reads
18302
18326
  * members those blocks do not have (`ReadUnitsPerSecond` vs
18303
18327
  * `ReadCapacityUnits`), so every declared capacity would read as undeclared.
@@ -18495,7 +18519,7 @@ var DynamoDBTableProvider = class {
18495
18519
  WriteCapacityUnits: tableCapacityForSend(pt, "WriteCapacityUnits", onUnusable)
18496
18520
  };
18497
18521
  }
18498
- if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = properties["OnDemandThroughput"];
18522
+ if (properties["OnDemandThroughput"]) createParams.OnDemandThroughput = this.coerceOnDemandCeilingsForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["OnDemandThroughput"], maskSecrets);
18499
18523
  const createWarmThroughput = this.coerceWarmThroughputForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["WarmThroughput"], maskSecrets);
18500
18524
  if (createWarmThroughput) createParams.WarmThroughput = createWarmThroughput;
18501
18525
  else this.warnRefusedWarmThroughput(`AWS::DynamoDB::Table ${logicalId}`, properties["WarmThroughput"], maskSecrets);
@@ -18664,7 +18688,7 @@ var DynamoDBTableProvider = class {
18664
18688
  if (properties["OnDemandThroughput"]) {
18665
18689
  await this.dynamoDBClient.send(new UpdateTableCommand({
18666
18690
  TableName: physicalId,
18667
- OnDemandThroughput: properties["OnDemandThroughput"]
18691
+ OnDemandThroughput: this.coerceOnDemandCeilingsForSend(`AWS::DynamoDB::Table ${logicalId}`, properties["OnDemandThroughput"], maskSecrets)
18668
18692
  }));
18669
18693
  await this.waitForTableActiveAfterUpdate(physicalId);
18670
18694
  this.logger.debug(`Updated OnDemandThroughput on DynamoDB table ${physicalId}`);
@@ -19334,7 +19358,7 @@ var DynamoDBTableProvider = class {
19334
19358
  KeySchema: gsi.KeySchema,
19335
19359
  Projection: gsi.Projection,
19336
19360
  ...gsi.ProvisionedThroughput ? { ProvisionedThroughput: this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets) } : {},
19337
- ...gsi.OnDemandThroughput ? { OnDemandThroughput: gsi.OnDemandThroughput } : {},
19361
+ ...gsi.OnDemandThroughput ? { OnDemandThroughput: this.coerceOnDemandCeilingsForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.OnDemandThroughput, maskSecrets) } : {},
19338
19362
  ...createWarm ? { WarmThroughput: createWarm } : {}
19339
19363
  } });
19340
19364
  } else {
@@ -19443,16 +19467,20 @@ var DynamoDBTableProvider = class {
19443
19467
  return this.coerceIndexCapacityBlock(scope, declared, maskSecrets) ?? declared;
19444
19468
  }
19445
19469
  /**
19446
- * One `CreateTable` index entry with BOTH of its throughput blocks coerced —
19447
- * `WarmThroughput` (PR review round 6) and `ProvisionedThroughput` (issue
19448
- * [#3255](https://github.com/go-to-k/cdkd/issues/3255)).
19470
+ * One `CreateTable` index entry with ALL THREE of its throughput blocks
19471
+ * coerced — `WarmThroughput` (PR review round 6), `ProvisionedThroughput`
19472
+ * (issue [#3255](https://github.com/go-to-k/cdkd/issues/3255)) and
19473
+ * `OnDemandThroughput` (issue
19474
+ * [#3265](https://github.com/go-to-k/cdkd/issues/3265)).
19449
19475
  *
19450
19476
  * `create()` forwards the declared `GlobalSecondaryIndexes` array to
19451
19477
  * `CreateTable`, so it is a SEND SITE for the same per-index values the
19452
19478
  * update path coerces. `WarmThroughput` was the one the round-5 sweep
19453
19479
  * missed; `ProvisionedThroughput` was missed by round 6 AND by issue #3147's
19454
- * own enumeration, because this forwarder is a CAST rather than a call, so
19455
- * neither sweep's grep could see it. The BillingMode flip's per-index reader
19480
+ * own enumeration; `OnDemandThroughput` was missed a THIRD time, by #3265's
19481
+ * own three-site grep — all for one reason, that this forwarder is a CAST
19482
+ * rather than a call, so no sweep's grep could see it. The
19483
+ * BillingMode flip's per-index reader
19456
19484
  * ({@link readCapacityNumber}) has been on the grammar since #3147, so until
19457
19485
  * now create and flip disagreed about the same property: a CFn-legal
19458
19486
  * `ReadCapacityUnits: '7'` reached `CreateTable` as a STRING in a `number`
@@ -19464,18 +19492,20 @@ var DynamoDBTableProvider = class {
19464
19492
  * and a provider that edits it in place would change what state reports cdkd
19465
19493
  * sent.
19466
19494
  *
19467
- * An entry that is not a plain object, and one declaring NEITHER throughput
19468
- * block, is returned UNCHANGED — identity included. An entry that DOES
19469
- * declare one is rebuilt whenever that block's coercion changes anything;
19470
- * the `WarmThroughput` half rebuilds even for an already-numeric value,
19471
- * which is pre-existing behaviour and costs one spread per declared block.
19495
+ * An entry that is not a plain object, and one declaring NONE of the three
19496
+ * throughput blocks, is returned UNCHANGED — identity included. An entry that
19497
+ * DOES declare one is rebuilt whenever that block's coercion changes
19498
+ * anything; the `WarmThroughput` half rebuilds even for an already-numeric
19499
+ * value, which is pre-existing behaviour and costs one spread per declared
19500
+ * block.
19472
19501
  */
19473
19502
  coerceIndexThroughputForCreate(logicalId, entry, maskSecrets = (text) => text) {
19474
19503
  if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
19475
19504
  const bag = entry;
19476
19505
  const declaredWarm = bag["WarmThroughput"];
19477
19506
  const declaredCapacity = bag["ProvisionedThroughput"];
19478
- if (declaredWarm === void 0 && !isPlainCapacityBlock(declaredCapacity)) return entry;
19507
+ const declaredOnDemand = bag["OnDemandThroughput"];
19508
+ if (declaredWarm === void 0 && !isPlainCapacityBlock(declaredCapacity) && !isPlainCapacityBlock(declaredOnDemand)) return entry;
19479
19509
  const scope = this.indexScope(entry.IndexName, logicalId, maskSecrets);
19480
19510
  let out = entry;
19481
19511
  if (declaredWarm !== void 0) {
@@ -19494,6 +19524,13 @@ var DynamoDBTableProvider = class {
19494
19524
  ...out,
19495
19525
  ProvisionedThroughput: capacity
19496
19526
  };
19527
+ if (isPlainCapacityBlock(declaredOnDemand)) {
19528
+ const onDemand = this.coerceOnDemandCeilingsForSend(scope, declaredOnDemand, maskSecrets);
19529
+ if (onDemand !== declaredOnDemand) out = {
19530
+ ...out,
19531
+ OnDemandThroughput: onDemand
19532
+ };
19533
+ }
19497
19534
  return out;
19498
19535
  }
19499
19536
  /**
@@ -19535,9 +19572,14 @@ var DynamoDBTableProvider = class {
19535
19572
  * the template had declared it, with no later call to correct it. Only
19536
19573
  * the TABLE level defaults.
19537
19574
  * - **A block that is not a plain object is forwarded VERBATIM**, the
19538
- * fail-OPEN direction this file takes everywhere: an unresolved intrinsic
19539
- * or a mis-nested template value is AWS's to reject by name, and that is
19540
- * also the pre-existing behaviour of this forwarder.
19575
+ * fail-OPEN direction this file takes everywhere: a STRING, a NUMBER or an
19576
+ * ARRAY block is AWS's to reject by name, and that is also the pre-existing
19577
+ * behaviour of this forwarder. An unresolved intrinsic (`{Ref: 'X'}`) is
19578
+ * NOT an example of it — that IS a plain object, so it takes the member
19579
+ * loop and survives through the preserve-unknown-member rule below. Same
19580
+ * destination, different route; corrected by the review of issue
19581
+ * [#3265](https://github.com/go-to-k/cdkd/issues/3265), which found the
19582
+ * wrong example here and at {@link coerceOnDemandCeilingsForSend}.
19541
19583
  *
19542
19584
  * A member name the grammar does not know is preserved untouched, for the
19543
19585
  * same fail-open reason — a misspelled `ReadCapacityUnit` reaches AWS and is
@@ -19560,6 +19602,178 @@ var DynamoDBTableProvider = class {
19560
19602
  return rewritten;
19561
19603
  }
19562
19604
  /**
19605
+ * One `OnDemandThroughput` block for the wire, with each DECLARED member read
19606
+ * through CloudFormation's DynamoDB Integer grammar (issue
19607
+ * [#3265](https://github.com/go-to-k/cdkd/issues/3265)).
19608
+ *
19609
+ * Called from ALL FOUR send sites, which is the whole point — the block was
19610
+ * forwarded VERBATIM at every one of them, so a CFn-legal
19611
+ * `MaxReadRequestUnits: '100'` reached AWS as the STRING `"100"` in a `Long`
19612
+ * field and the request failed, while a `' 100 '` / `'0x64'` / `'1e2'`
19613
+ * CloudFormation refuses the template for was forwarded unchanged. The sites:
19614
+ *
19615
+ * 1. `create()`'s TABLE-level `CreateTable.OnDemandThroughput`;
19616
+ * 2. `create()`'s PER-INDEX `CreateTable.GlobalSecondaryIndexes[]` forward,
19617
+ * through {@link coerceIndexThroughputForCreate} — a CAST of the whole
19618
+ * entry array rather than a named `OnDemandThroughput` expression, which
19619
+ * is why issue #3265's own grep enumerated three sites and not four (the
19620
+ * same blind spot that hid this forwarder from issue #3147's sweep and
19621
+ * from #3255's round 6, one property over);
19622
+ * 3. `update()`'s TABLE-level `UpdateTable.OnDemandThroughput`;
19623
+ * 4. `applyGsiUpdates`' per-index `Create` action.
19624
+ *
19625
+ * FOUR and not six: `UpdateGlobalSecondaryIndexAction` declares
19626
+ * `OnDemandThroughput` too, and neither the adopted-index repair nor the
19627
+ * same-name `Update` arm ever sets it — so a ceiling EDIT on a live index is
19628
+ * silently discarded today. That is a MISSING SEND rather than a grammar
19629
+ * defect, so it is not routed here; issue
19630
+ * [#3287](https://github.com/go-to-k/cdkd/issues/3287) holds it, and it will
19631
+ * read through this function when it lands.
19632
+ *
19633
+ * THREE decisions, each with a plausible alternative:
19634
+ *
19635
+ * - **A rejected spelling drops the MEMBER, never the BLOCK.** The same
19636
+ * DECISION {@link coerceIndexCapacityBlock} and {@link tableCapacityForSend}
19637
+ * already take one property over, so this file's capacity and ceiling
19638
+ * families answer identically about one template value; it keeps a sibling
19639
+ * member the template spelled LEGALLY; and it leaves `indexDeclares`'
19640
+ * TRUTHINESS gate ("would cdkd SEND this block") true without
19641
+ * qualification, so the #1767 drift-side predicate needs no new arm. An
19642
+ * all-rejected block therefore goes out EMPTY, as the capacity sibling's
19643
+ * does. ("The same decision", NOT the same SIGNATURE — the capacity
19644
+ * sibling answers `undefined` for "nothing to rewrite" while this one
19645
+ * returns the declared block by IDENTITY. The review of this issue caught
19646
+ * an earlier "byte for byte the shape" here, which was false and is the
19647
+ * kind of claim that invites a caller to swap one for the other.)
19648
+ * - **What a rejected spelling COSTS differs from the capacity sibling's,
19649
+ * and it is stated at {@link warnUnusableOnDemandCeiling} rather than
19650
+ * assumed.** Both `OnDemandThroughput` members are SDK-optional, so
19651
+ * DynamoDB does not necessarily reject a request that omits one — which is
19652
+ * why the announcement cannot reuse the capacity wording, which promises
19653
+ * AWS will name the member. **It is NOT a promise of success either**, and
19654
+ * an earlier revision made it one: the outcome is per PATH (create leaves
19655
+ * no maximum, update KEEPS the live one since only `-1` removes it) and an
19656
+ * ALL-rejected block is documented invalid at every one of the four send
19657
+ * positions ("you must specify `MaxReadRequestUnits`,
19658
+ * `MaxWriteRequestUnits`, or both"), so that request should FAIL. cdkd has
19659
+ * not measured the empty-block send against live DynamoDB; the message
19660
+ * under-claims accordingly.
19661
+ * - **A block that is not a plain object is forwarded VERBATIM**, the
19662
+ * fail-OPEN direction this file takes everywhere: a STRING, a NUMBER or an
19663
+ * ARRAY `OnDemandThroughput` is AWS's to reject by name, and that is the
19664
+ * pre-existing behaviour of every one of these forwarders. Note what this
19665
+ * arm does NOT cover: an unresolved intrinsic (`{Ref: 'X'}`) IS a plain
19666
+ * object, so it takes the member loop instead and survives through the
19667
+ * preserve-unknown-member rule below — same destination, different route.
19668
+ * {@link isPlainCapacityBlock}'s own doc is the authority; the review of
19669
+ * this issue found that example wrong here and at
19670
+ * {@link coerceIndexCapacityBlock}.
19671
+ *
19672
+ * A member name the grammar does not know is preserved untouched, for the
19673
+ * same fail-open reason. Returns the DECLARED object by IDENTITY when nothing
19674
+ * needs rewriting, so a numeric template rebuilds nothing and
19675
+ * {@link coerceIndexThroughputForCreate} can tell "unchanged" from "rewritten"
19676
+ * without a structural compare.
19677
+ *
19678
+ * **Known residual, deliberately not closed here.** Wherever the call
19679
+ * SUCCEEDS, state records the spelling the TEMPLATE declared while AWS holds
19680
+ * a different ceiling (none on a create, the previous one on an update) — the
19681
+ * permanent phantom-drift class
19682
+ * `.claude/rules/provider-property-fidelity.md` covers, which
19683
+ * {@link coerceIndexCapacityBlock} escapes only because ITS drop makes AWS
19684
+ * fail. Closing it needs `effectiveProperties` AND its
19685
+ * `canonicalizeDesiredProperties` twin (the rule requires the pair; shipping
19686
+ * the first alone re-issues a no-op `UpdateTable` on every later deploy), and
19687
+ * this provider implements neither today. Tracked as issue
19688
+ * [#3286](https://github.com/go-to-k/cdkd/issues/3286); the warning names the
19689
+ * residual so a user is not left to discover it from a `cdkd drift` line. The
19690
+ * `AWS::DynamoDB::GlobalTable` sibling answers the SAME way for the same
19691
+ * spelling — `collectTableOnDemandCeilings` coerces through
19692
+ * `coerceCfnInteger` and a rejected ceiling is reported and LEFT UNSENT, with
19693
+ * AWS untouched and no substituted value (its 5/5 FALLBACK is its PROVISIONED
19694
+ * capacity answer, not its ceiling one) — so the two types do not diverge.
19695
+ */
19696
+ coerceOnDemandCeilingsForSend(scope, declared, maskSecrets) {
19697
+ if (!isPlainCapacityBlock(declared)) return declared;
19698
+ const block = declared;
19699
+ let rewritten;
19700
+ for (const member of ON_DEMAND_CEILING_MEMBERS) {
19701
+ if (!(member in block)) continue;
19702
+ const coerced = coerceCfnInteger(block[member]);
19703
+ if (coerced === block[member]) continue;
19704
+ rewritten ??= { ...block };
19705
+ if (coerced === void 0) {
19706
+ this.warnUnusableOnDemandCeiling(scope, member, block[member], maskSecrets);
19707
+ delete rewritten[member];
19708
+ } else rewritten[member] = coerced;
19709
+ }
19710
+ return rewritten ?? block;
19711
+ }
19712
+ /**
19713
+ * Announce an `OnDemandThroughput` member a forwarder DROPPED (issue
19714
+ * [#3265](https://github.com/go-to-k/cdkd/issues/3265)).
19715
+ *
19716
+ * A SEPARATE sentence from {@link warnUnusableProvisionedCapacity}, and the
19717
+ * difference is the load-bearing part rather than a wording preference: that
19718
+ * one can promise "DynamoDB will reject the request naming this member",
19719
+ * because both `ProvisionedThroughput` members are REQUIRED wherever the
19720
+ * block is declared. Both `OnDemandThroughput` members are OPTIONAL, so the
19721
+ * same promise would send the user looking for a failure that may never come.
19722
+ *
19723
+ * What it says instead is PER PATH, because the outcome genuinely differs and
19724
+ * an earlier revision of this message asserted ONE outcome for all of them —
19725
+ * wrongly in both directions (the review of this issue):
19726
+ *
19727
+ * - on a CREATE the table comes up with no maximum for the dropped half;
19728
+ * - on an UPDATE DynamoDB KEEPS whatever maximum the table already carries.
19729
+ * Omitting a member is NOT "no maximum" there — `-1` is AWS's documented
19730
+ * way to REMOVE one (`OnDemandThroughput`'s own SDK model doc), so a
19731
+ * dropped member silently leaves the live ceiling in force;
19732
+ * - when NO member survived, the block {@link coerceOnDemandCeilingsForSend}
19733
+ * sends is EMPTY. AWS's model says at all four of this provider's send
19734
+ * positions that "if you use this parameter, you must specify
19735
+ * `MaxReadRequestUnits`, `MaxWriteRequestUnits`, or both", which reads
19736
+ * like a LOUD failure — and it is NOT enforced.
19737
+ *
19738
+ * MEASURED us-east-1 2026-09-17, on the go-to-k/cdkd#3291 review's
19739
+ * request: `CreateTable` with `OnDemandThroughput: {}` and
19740
+ * `BillingMode: PAY_PER_REQUEST` is ACCEPTED, the table reaches ACTIVE,
19741
+ * and `DescribeTable` reports NO `OnDemandThroughput` at all. So the
19742
+ * all-rejected case is the SILENT one, not the loud one: the deploy goes
19743
+ * green with no ceiling applied.
19744
+ *
19745
+ * That matters because it is a REGRESSION IN LOUDNESS introduced by this
19746
+ * very change. Before it, a `' 100 '` reached AWS as a string in a Long
19747
+ * field and the request FAILED, certainly. After it, the value is dropped
19748
+ * and the deploy succeeds. The warning is therefore the ONLY signal a user
19749
+ * gets for the all-rejected case, which is why it is emitted per member
19750
+ * and why its text was corrected to say so rather than to promise a
19751
+ * failure that does not come.
19752
+ *
19753
+ * The caller cannot tell the three cases apart from inside the member loop,
19754
+ * so the message names all three rather than asserting the one it cannot
19755
+ * know.
19756
+ *
19757
+ * And it names the residual: where the request does succeed, state keeps the
19758
+ * spelling the template declared and `cdkd drift` reports it until the
19759
+ * template is corrected. `-1` is called out because a user who meant the
19760
+ * removal must not read "spell it as a decimal integer" as excluding it.
19761
+ *
19762
+ * The CloudFormation clause is HEDGED ("is expected to"), and deliberately.
19763
+ * The live A/B that settled this grammar was run on
19764
+ * `ProvisionedThroughput.ReadCapacityUnits`, not on `MaxReadRequestUnits`.
19765
+ * Both are Integer members of the same type validated at the same properties
19766
+ * layer, so the extrapolation is sound and is what issue #3265 reasons from --
19767
+ * but it IS an extrapolation, and a user-facing string asserting it flatly
19768
+ * about an unmeasured property would be the kind of claim nothing downstream
19769
+ * re-checks. Measure it and the hedge can go. Raised by the
19770
+ * go-to-k/cdkd#3291 spec review.
19771
+ */
19772
+ warnUnusableOnDemandCeiling(scope, member, raw, maskSecrets) {
19773
+ const warn = (message) => this.logger.warn(maskSecrets(message));
19774
+ warn(`${scope}: OnDemandThroughput.${member} ${JSON.stringify(maskLeafValue$1(raw, maskSecrets))} is not an Integer CloudFormation accepts (an optional sign and decimal digits only — no surrounding whitespace, hex, exponent or decimal point), so it was NOT sent and NO ceiling was substituted for it. Both OnDemandThroughput members are OPTIONAL to DynamoDB, so — unlike a ProvisionedThroughput member — AWS does not necessarily reject the request, and what happens next depends on the path: a CREATE gets no maximum for this half; an UPDATE KEEPS whatever maximum the table already carries, since only an explicit -1 removes one; and if NEITHER member was usable the block is sent EMPTY, which AWS ACCEPTS (measured us-east-1 2026-09-17) and stores as no maximum at all, so the deploy SUCCEEDS with no ceiling applied. cdkd's state keeps the value the template declared and \`cdkd drift\` reports the difference until the template is corrected; CloudFormation is expected to refuse the same template at properties validation. Check for an unresolved intrinsic, or spell the value as a decimal integer (\`-1\` removes an existing maximum).`);
19775
+ }
19776
+ /**
19563
19777
  * Announce a `ProvisionedThroughput` member a forwarder DROPPED (issue
19564
19778
  * [#3147](https://github.com/go-to-k/cdkd/issues/3147); widened to the
19565
19779
  * per-index create forwarder by issue
@@ -62206,6 +62420,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62206
62420
  const guardIndeterminateTargets = /* @__PURE__ */ new Set();
62207
62421
  const regionForState = state.region ?? ctx.baseRegion;
62208
62422
  refuseMalformedResourcesForDestroy(state, stackName, regionForState);
62423
+ refuseDivergentRecordRegionForDestroy(state, stackName, regionForState, ctx.divergentBodyRegion);
62209
62424
  const resourceCount = Object.keys(state.resources).length;
62210
62425
  const orphanCount = (state.orphans ?? []).length;
62211
62426
  refuseMalformedOutputsForDestroy(state, stackName, regionForState);
@@ -62265,7 +62480,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62265
62480
  try {
62266
62481
  await ctx.lockManager.releaseLock(stackName, regionForState);
62267
62482
  } catch (releaseErr) {
62268
- logger.warn(`Failed to release lock after empty-state cleanup: ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
62483
+ logger.warn(`Failed to release lock after empty-state cleanup: ${describeAwsFailure(releaseErr).detail}`);
62269
62484
  } finally {
62270
62485
  process.removeListener("SIGINT", emptySigintHandler);
62271
62486
  }
@@ -62324,7 +62539,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62324
62539
  try {
62325
62540
  switchedClients?.destroy();
62326
62541
  } catch (destroyError) {
62327
- logger.debug(`Failed to destroy region-scoped AWS clients: ${destroyError instanceof Error ? destroyError.message : String(destroyError)}`);
62542
+ logger.debug(`Failed to destroy region-scoped AWS clients: ${describeAwsFailure(destroyError).detail}`);
62328
62543
  }
62329
62544
  };
62330
62545
  if (stackRegion && stackRegion !== ctx.baseRegion) {
@@ -62393,7 +62608,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62393
62608
  try {
62394
62609
  await ctx.lockManager.releaseLock(stackName, regionForState);
62395
62610
  } catch (releaseErr) {
62396
- logger.warn(`Failed to release lock after strong-ref refusal/failure: ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
62611
+ logger.warn(`Failed to release lock after strong-ref refusal/failure: ${describeAwsFailure(releaseErr).detail}`);
62397
62612
  }
62398
62613
  restoreBaseRegionAndClients();
62399
62614
  process.removeListener("SIGINT", sigintHandler);
@@ -62433,7 +62648,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62433
62648
  await ctx.stateBackend.saveState(stackName, regionForState, buildDestroySnapshot());
62434
62649
  logger.debug(`State persisted after deleting ${logicalId}`);
62435
62650
  } catch (error) {
62436
- logger.warn(`Failed to persist state after deleting ${logicalId} (continuing): ${error instanceof Error ? error.message : String(error)}`);
62651
+ logger.warn(`Failed to persist state after deleting ${logicalId} (continuing): ${describeAwsFailure(error).detail}`);
62437
62652
  }
62438
62653
  });
62439
62654
  };
@@ -62629,7 +62844,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62629
62844
  persistStateAfterDelete(logicalId);
62630
62845
  } catch (error) {
62631
62846
  renderer.removeTask(logicalId);
62632
- const msg = error instanceof Error ? error.message : String(error);
62847
+ const msg = describeAwsFailure(error).detail;
62633
62848
  if (!isInterruptedWaitError(error) && !isFinalSnapshotError(error) && !isMarkedNonRetryable(error) && !isWaitAbandonedError(error) && (msg.includes("does not exist") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException"))) {
62634
62849
  logger.debug(` ${logicalId} already deleted, removing from state`);
62635
62850
  result.deletedCount++;
@@ -62661,7 +62876,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62661
62876
  error: extractDeploymentEventError(wrapped)
62662
62877
  });
62663
62878
  } else {
62664
- logger.error(` ✗ Failed to delete ${logicalId}:`, String(error));
62879
+ logger.error(` ✗ Failed to delete ${logicalId}:`, safeStringify(error));
62665
62880
  result.errorCount++;
62666
62881
  failedStateTargets.add(stateTargetFor(logicalId, resource.resourceType));
62667
62882
  ctx.eventRecorder?.record({
@@ -62693,7 +62908,7 @@ async function runDestroyForStack(stackName, state, ctx) {
62693
62908
  try {
62694
62909
  await ctx.stateBackend.saveState(stackName, regionForState, buildDestroySnapshot());
62695
62910
  } catch (error) {
62696
- logger.warn(`Failed to persist remaining state after partial destroy: ${error instanceof Error ? error.message : String(error)}. The state file may still list already-deleted resources; a re-run resolves them idempotently.`);
62911
+ logger.warn(`Failed to persist remaining state after partial destroy: ${describeAwsFailure(error).detail}. The state file may still list already-deleted resources; a re-run resolves them idempotently.`);
62697
62912
  }
62698
62913
  if (result.interrupted) logger.warn(`Destroy interrupted — ${Object.keys(remainingResources).length} resource(s) not deleted. State preserved.`);
62699
62914
  else if (result.errorCount > 0) logger.warn(`${result.errorCount} resource(s) failed to delete. State preserved.`);
@@ -62724,14 +62939,14 @@ async function runDestroyForStack(stackName, state, ctx) {
62724
62939
  try {
62725
62940
  renderer.stop();
62726
62941
  } catch (rendererError) {
62727
- logger.debug(`Live renderer teardown failed (continuing to release the lock): ${rendererError instanceof Error ? rendererError.message : String(rendererError)}`);
62942
+ logger.debug(`Live renderer teardown failed (continuing to release the lock): ${describeAwsFailure(rendererError).detail}`);
62728
62943
  }
62729
62944
  await saveChain;
62730
62945
  logger.debug("Releasing lock...");
62731
62946
  try {
62732
62947
  await ctx.lockManager.releaseLock(stackName, regionForState);
62733
62948
  } catch (releaseErr) {
62734
- logger.warn(`Failed to release lock for stack '${stackName}': ${releaseErr instanceof Error ? releaseErr.message : String(releaseErr)}`);
62949
+ logger.warn(`Failed to release lock for stack '${stackName}': ${describeAwsFailure(releaseErr).detail}`);
62735
62950
  }
62736
62951
  } finally {
62737
62952
  process.removeListener("SIGINT", sigintHandler);
@@ -63004,6 +63219,7 @@ var NestedStackProvider = class {
63004
63219
  providerRegistry: ctx.providerRegistry,
63005
63220
  baseAwsClients: ctx.awsClients,
63006
63221
  baseRegion: childRegion,
63222
+ ...childStateData.divergentBodyRegion !== void 0 && { divergentBodyRegion: childStateData.divergentBodyRegion },
63007
63223
  stateBucket: ctx.stateBucket,
63008
63224
  skipConfirmation: true,
63009
63225
  ...ctx.exportIndexStore && { exportIndexStore: ctx.exportIndexStore },
@@ -64378,7 +64594,8 @@ async function deployCommand(stacks, options) {
64378
64594
  clients: {
64379
64595
  s3: stackAwsClients.s3,
64380
64596
  cloudWatchLogs: stackAwsClients.cloudWatchLogs,
64381
- expectedRegion: stateForRecreateCheck?.state.region
64597
+ expectedRegion: stateForRecreateCheck?.state.region,
64598
+ ...stateForRecreateCheck?.divergentBodyRegion !== void 0 && { expectedRegionDiverged: true }
64382
64599
  },
64383
64600
  forceStatefulRecreation: options.forceStatefulRecreation ?? false
64384
64601
  });
@@ -69889,6 +70106,28 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69889
70106
  state: scrubbed
69890
70107
  };
69891
70108
  });
70109
+ const crossStackNeedles = allRecordedSecrets(outputSecrets, perResourceSecrets, orphanSecrets);
70110
+ const redactCrossStackName = (name) => redactSecretsForState(name, crossStackNeedles);
70111
+ const newImports = state.imports?.map((entry) => {
70112
+ const exportName = redactCrossStackName(entry.exportName);
70113
+ if (exportName === entry.exportName) return entry;
70114
+ recordsChanged++;
70115
+ return {
70116
+ ...entry,
70117
+ exportName
70118
+ };
70119
+ });
70120
+ const newOutputReads = state.outputReads?.map((entry) => {
70121
+ const sourceStack = redactCrossStackName(entry.sourceStack);
70122
+ const outputName = redactCrossStackName(entry.outputName);
70123
+ if (sourceStack === entry.sourceStack && outputName === entry.outputName) return entry;
70124
+ recordsChanged++;
70125
+ return {
70126
+ ...entry,
70127
+ sourceStack,
70128
+ outputName
70129
+ };
70130
+ });
69892
70131
  const newOutputs = redactUnaccountedOutputs(outputSecrets.size > 0 ? redactSecretsForState(state.outputs, outputSecrets, outputsSourceUntrusted ? void 0 : outputsTemplateSource, TEMPLATE_SOURCED_RULES) : state.outputs, state.outputs, accountedOutputKeys, allRecordedSecrets(outputSecrets, perResourceSecrets, orphanSecrets));
69893
70132
  emitAbandonedScanNotes();
69894
70133
  if (JSON.stringify(newOutputs) !== JSON.stringify(state.outputs)) recordsChanged++;
@@ -69898,6 +70137,8 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
69898
70137
  ...carriedState,
69899
70138
  resources: newResources,
69900
70139
  ...state.orphans !== void 0 && { orphans: newOrphans },
70140
+ ...newImports !== void 0 && { imports: newImports },
70141
+ ...newOutputReads !== void 0 && { outputReads: newOutputReads },
69901
70142
  outputs: newOutputs,
69902
70143
  lastModified: Date.now()
69903
70144
  };
@@ -74363,6 +74604,7 @@ async function destroyCommand(stackArgs, options) {
74363
74604
  providerRegistry,
74364
74605
  baseAwsClients: awsClients,
74365
74606
  baseRegion: region,
74607
+ ...stateResult.divergentBodyRegion !== void 0 && { divergentBodyRegion: stateResult.divergentBodyRegion },
74366
74608
  ...options.profile && { profile: options.profile },
74367
74609
  stateBucket,
74368
74610
  statePrefix: options.statePrefix,
@@ -76117,10 +76359,10 @@ async function exportCommand(stackArg, options) {
76117
76359
  }
76118
76360
  const cfnStackName = options.cfnStackName ?? resolvedStackName;
76119
76361
  const targetRegion = await pickStackRegion$1(stateBackend, resolvedStackName, synthedRegion, options.stackRegion);
76120
- logger.info(`Migrating cdkd stack '${resolvedStackName}' (${targetRegion}) → CloudFormation stack '${cfnStackName}'`);
76362
+ logger.info(`Migrating cdkd stack '${resolvedStackName}' (${safeSegment(targetRegion)}) → CloudFormation stack '${cfnStackName}'`);
76121
76363
  await assertCfnStackAbsent(awsClients.cloudFormation, cfnStackName);
76122
76364
  const stateData = await stateBackend.getState(resolvedStackName, targetRegion);
76123
- if (!stateData) throw new Error(`No cdkd state found for stack '${resolvedStackName}' (${targetRegion}). Nothing to migrate.`);
76365
+ if (!stateData) throw new Error(`No cdkd state found for stack '${resolvedStackName}' (${safeSegment(targetRegion)}). Nothing to migrate.`);
76124
76366
  const { state, etag, migrationPending } = stateData;
76125
76367
  const exportJournal = await stateBackend.loadRollbackJournal(resolvedStackName, targetRegion);
76126
76368
  if (exportJournal && exportJournal.segments.length > 0) {
@@ -76237,7 +76479,7 @@ async function exportCommand(stackArg, options) {
76237
76479
  const phase2Note = phase2Creates.length > 0 ? ` Phase 2 will then CREATE ${phase2Creates.length} non-importable resource(s) (invoking each Custom Resource's onCreate handler).` : "";
76238
76480
  const recreateNote = recreateBeforePhase2.length > 0 ? ` cdkd will also DELETE ${recreateBeforePhase2.length} AWS resource(s) between phases so CFn can re-CREATE them in phase 2 (brief unavailability window — see plan above for the affected resources).` : "";
76239
76481
  const unchangedClaim = recreateBeforePhase2.length > 0 ? ` All other AWS resources are unchanged on import.` : ` AWS resources are unchanged on import.`;
76240
- if (!await confirmPrompt$3(`Create CloudFormation stack '${cfnStackName}' by importing ${phase1Imports.length} resource(s) from cdkd state '${resolvedStackName}' (${targetRegion})?` + phase2Note + recreateNote + unchangedClaim + ` cdkd state for '${resolvedStackName}' will be deleted on success.`)) {
76482
+ if (!await confirmPrompt$3(`Create CloudFormation stack '${cfnStackName}' by importing ${phase1Imports.length} resource(s) from cdkd state '${resolvedStackName}' (${safeSegment(targetRegion)})?` + phase2Note + recreateNote + unchangedClaim + ` cdkd state for '${resolvedStackName}' will be deleted on success.`)) {
76241
76483
  logger.info("Migration cancelled. cdkd state and CloudFormation are unchanged.");
76242
76484
  return;
76243
76485
  }
@@ -76272,7 +76514,7 @@ async function exportCommand(stackArg, options) {
76272
76514
  logger.info(` ✓ deleted ${entry.physicalId}`);
76273
76515
  } catch (err) {
76274
76516
  const msg = err instanceof Error ? err.message : String(err);
76275
- throw new Error(`Phase 1 (IMPORT) succeeded; pre-delete of ${entry.logicalId} (${entry.resourceType}, physicalId: ${entry.physicalId}) failed: ${msg}\n\nThe CloudFormation stack '${cfnStackName}' contains the phase-1 imports but the IMPORT-unsupported resources (${recreateBeforePhase2.length} total) still exist in AWS unmanaged. cdkd state is UNCHANGED. Re-running \`cdkd export\` does NOT work — the existing-stack check rejects it. To recover manually:\n 1. Fix the failure cause (typically IAM permissions for the underlying AWS API — e.g. apigatewayv2:DeleteStage for AWS::ApiGatewayV2::Stage).\n 2. Delete the remaining AWS-side IMPORT-unsupported resources by hand:\n aws apigatewayv2 delete-stage --api-id <ApiId> --stage-name <StageName>\n (one per entry in the pre-delete list logged above).\n 3. Run the phase-2 UPDATE manually with the full synth template:\n aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 4. Once phase 2 succeeds, run: cdkd state orphan ${resolvedStackName}\n to clean up cdkd's stale state record.`);
76517
+ throw new Error(`Phase 1 (IMPORT) succeeded; pre-delete of ${entry.logicalId} (${entry.resourceType}, physicalId: ${entry.physicalId}) failed: ${msg}\n\nThe CloudFormation stack '${cfnStackName}' contains the phase-1 imports but the IMPORT-unsupported resources (${recreateBeforePhase2.length} total) still exist in AWS unmanaged. cdkd state is UNCHANGED. Re-running \`cdkd export\` does NOT work — the existing-stack check rejects it. To recover manually:\n 1. Fix the failure cause (typically IAM permissions for the underlying AWS API — e.g. apigatewayv2:DeleteStage for AWS::ApiGatewayV2::Stage).\n 2. Delete the remaining AWS-side IMPORT-unsupported resources by hand:\n aws apigatewayv2 delete-stage --api-id <ApiId> --stage-name <StageName>\n (one per entry in the pre-delete list logged above).\n 3. Run the phase-2 UPDATE manually with the full synth template:\n aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 4. Once phase 2 succeeds, run: ${orphanCommandFor(resolvedStackName, targetRegion)}\n to clean up cdkd's stale state record.`);
76276
76518
  }
76277
76519
  }
76278
76520
  const phase2Count = phase2Creates.length + recreateBeforePhase2.length;
@@ -76291,10 +76533,10 @@ async function exportCommand(stackArg, options) {
76291
76533
  const recreateNote = recreateBeforePhase2.length > 0 ? ` - cdkd deleted ${recreateBeforePhase2.length} AWS resource(s) before phase 2 (Stage etc.). They are gone in AWS but absent from the CFn stack. Running phase 2 UPDATE manually will CFn-CREATE them fresh.\n` : "";
76292
76534
  throw new Error(`Phase 1 (IMPORT) succeeded; phase 2 (UPDATE) failed: ${msg}\n\nThe CloudFormation stack '${cfnStackName}' now contains the imported resources but is missing ${phase2Count} resource(s) (${phase2Creates.length} Custom Resource(s) + ${recreateBeforePhase2.length} IMPORT-unsupported). cdkd state is UNCHANGED so you can inspect what's in it, but DO NOT run \`cdkd deploy\` against this stack (the imported resources are now CFn-managed). To recover:\n` + recreateNote + ` 1. Fix the failure cause (typically an onCreate Lambda error).
76293
76535
  2. Re-run the phase 2 UPDATE manually with the full synth template:
76294
- aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 3. Once phase 2 succeeds, run: cdkd state orphan ${resolvedStackName}\n to clean up cdkd's stale state record.`);
76536
+ aws cloudformation create-change-set --stack-name ${cfnStackName} \\\n --change-set-name cdkd-phase2-retry --change-set-type UPDATE \\\n --template-body file://<full-template.json>\n 3. Once phase 2 succeeds, run: ${orphanCommandFor(resolvedStackName, targetRegion)}\n to clean up cdkd's stale state record.`);
76295
76537
  }
76296
76538
  await stateBackend.deleteState(resolvedStackName, targetRegion);
76297
- logger.info(`cdkd state for '${resolvedStackName}' (${targetRegion}) removed. Manage the stack with 'cdk deploy' or 'aws cloudformation' from here on.`);
76539
+ logger.info(`cdkd state for '${resolvedStackName}' (${safeSegment(targetRegion)}) removed. Manage the stack with 'cdk deploy' or 'aws cloudformation' from here on.`);
76298
76540
  printNextSteps({
76299
76541
  cfnStackName,
76300
76542
  cdkStackName: resolvedStackName,
@@ -76322,7 +76564,7 @@ async function pickStackRegion$1(stateBackend, stackName, synthRegion, flag) {
76322
76564
  }
76323
76565
  if (flag) {
76324
76566
  if (!refs.find((r) => r.region === flag)) {
76325
- const seen = refs.map((r) => r.region ?? "(legacy)").join(", ");
76567
+ const seen = refs.map((r) => safeSegment(r.region ?? "(legacy)")).join(", ");
76326
76568
  throw new Error(`No state found for stack '${stackName}' in region '${flag}'. Available regions: ${seen}.`);
76327
76569
  }
76328
76570
  return flag;
@@ -76331,7 +76573,7 @@ async function pickStackRegion$1(stateBackend, stackName, synthRegion, flag) {
76331
76573
  if (refs.find((r) => r.region === synthRegion)) return synthRegion;
76332
76574
  }
76333
76575
  if (refs.length === 1) return refs[0].region ?? synthRegion ?? "";
76334
- const regions = refs.map((r) => r.region ?? "(legacy)").join(", ");
76576
+ const regions = refs.map((r) => safeSegment(r.region ?? "(legacy)")).join(", ");
76335
76577
  throw new Error(`Stack '${stackName}' has state in multiple regions: ${regions}. Re-run with --stack-region <region> to disambiguate.`);
76336
76578
  }
76337
76579
  function parseTemplateFile(path) {
@@ -76395,9 +76637,81 @@ function isPhase2CreatableType(resourceType) {
76395
76637
  * Module-scoped rather than local to the walker: the first cut of this change
76396
76638
  * guarded the walker's two refusals and left the root one, which is the shape
76397
76639
  * the issue is about.
76640
+ *
76641
+ * The parameter is `unknown`, not `string | undefined`, because a record-body
76642
+ * value is only TYPED as a string: the region-mismatch refusal below has
76643
+ * rendered `{"toString": null}` since issue #2947, and `getState`'s
76644
+ * `divergentBodyRegion` (#3328) is declared honestly as `unknown`.
76645
+ * `displaySafe` has always accepted `unknown` and coerces totally, so this
76646
+ * widens the signature to what the function already did.
76647
+ *
76648
+ * CAPPED as well as sanitized, since go-to-k/cdkd#3328 review round 2. Every
76649
+ * value reaching here is a state-record field or an S3 key segment, so a
76650
+ * planted multi-kilobyte one — or an ARRAY, which `String` comma-joins with no
76651
+ * bound — pushes the rest of the refusal off the reader's screen. The bound is
76652
+ * the STATE-RECORD grammar rather than `IDENT_MAX_CODE_POINTS` because a
76653
+ * legitimate multi-level nested-child stack name runs past 255 code points,
76654
+ * and this function renders those; `truncateCodePoints` so a cut never lands
76655
+ * inside a surrogate pair.
76398
76656
  */
76399
76657
  function safeSegment(value) {
76400
- return displaySafe(value, { asciiOnly: true }) || "<unrenderable>";
76658
+ const safe = displaySafe(value, { asciiOnly: true });
76659
+ if (!safe) return UNRENDERABLE;
76660
+ const { text, truncated } = truncateCodePoints(safe, STACK_REF_MAX_CODE_POINTS);
76661
+ return truncated ? `${text} [cut: ${safe.length - text.length} more characters withheld]` : text;
76662
+ }
76663
+ /**
76664
+ * The free-form counterpart of {@link safeSegment}: an AWS error message, not
76665
+ * an identifier.
76666
+ *
76667
+ * **Its BEHAVIOUR is identical today, and the separation is about the CALLER's
76668
+ * contract rather than about the transformation** — stated plainly because an
76669
+ * earlier revision of this comment claimed a "tighter cap" for identifiers that
76670
+ * does not exist, and a false distinction is worse than none. What differs is
76671
+ * what a caller may DO with the result: `safeSegment`'s callers compare it
76672
+ * against the raw value to decide whether an identity renders EXACTLY
76673
+ * ({@link orphanCommandFor}), which is meaningless for a sentence; and
76674
+ * `display-safe.ts` says its identifier helpers are not for free-form text, so
76675
+ * routing a message through the one named `Segment` invites the next reader to
76676
+ * give it the identifier grammar's cap. Both sanitize with the ASCII allowlist
76677
+ * and bound the length, because both are untrusted text bound for a terminal.
76678
+ */
76679
+ function safeDetail(value) {
76680
+ const safe = displaySafe(value instanceof Error ? value.message : value, { asciiOnly: true });
76681
+ if (!safe) return UNRENDERABLE;
76682
+ const { text, truncated } = truncateCodePoints(safe, STACK_REF_MAX_CODE_POINTS);
76683
+ return truncated ? `${text} [cut: ${safe.length - text.length} more characters withheld]` : text;
76684
+ }
76685
+ /**
76686
+ * A `cdkd state orphan` line for a record-derived stack name — PASTEABLE only
76687
+ * when that name renders EXACTLY, and a template otherwise (issue
76688
+ * [#3328](https://github.com/go-to-k/cdkd/issues/3328), review round 3).
76689
+ *
76690
+ * Three messages in this file suggested that command with a name minted from
76691
+ * `Object.keys(state.resources)` (`walkCdkdStateStackTree` builds
76692
+ * `` `${parent}~${logicalId}` ``), i.e. body content anyone able to write one
76693
+ * state key chooses. Two rendered it RAW, so a newline or a CSI sequence in a
76694
+ * logical id forged whole lines around a DELETE command; the third sanitized
76695
+ * but did not shell-quote, so `'`, `;`, `|` and `` ` `` survived into a line an
76696
+ * operator pastes into a shell.
76697
+ *
76698
+ * Both halves of `malformed-resources-bag.ts`'s rule, and for its reasons:
76699
+ * sanitize THEN shell-quote, and gate the substituted form on the identity
76700
+ * rendering exactly — `displaySafe` TRIMS, so a name ending in a space renders
76701
+ * byte-identically to a healthy sibling, and `state orphan` DELETES a record.
76702
+ * Every caller emits the result LAST and unwrapped so the quoting composes; that
76703
+ * is an instruction to the CALLER and must not leak into the message it builds.
76704
+ *
76705
+ * `region` is REQUIRED even though the flag is optional on the command: omitting
76706
+ * `--stack-region` makes `state orphan` drop the record for that name in EVERY
76707
+ * region, which is wider than any of these messages describes. A helper whose
76708
+ * job is to make a destructive suggestion safe should not have an arm that
76709
+ * widens one.
76710
+ */
76711
+ function orphanCommandFor(stackName, region) {
76712
+ const shownStack = safeSegment(stackName);
76713
+ if (!(typeof stackName === "string" && shownStack === stackName && typeof region === "string" && safeSegment(region) === region)) return "cdkd state orphan <stack> --stack-region <region> — spelled out because this record's name or region does NOT render exactly, so another record may render identically; list them as stored with 'cdkd state list --long' and act on the one whose key matches";
76714
+ return `cdkd state orphan ${shellQuote(shownStack)} --stack-region ${shellQuote(safeSegment(region))}`;
76401
76715
  }
76402
76716
  /**
76403
76717
  * Recursively load the cdkd-state tree rooted at `(rootStackName, region)`.
@@ -76452,8 +76766,8 @@ async function walkCdkdStateStackTree(stackName, region, state, stateBackend) {
76452
76766
  if (entry?.resourceType !== "AWS::CloudFormation::Stack") continue;
76453
76767
  const childStackName = `${stackName}~${logicalId}`;
76454
76768
  const childResult = await stateBackend.getState(childStackName, region);
76455
- if (!childResult) throw new Error(`cdkd state is missing nested-child '${safeSegment(childStackName)}' (${safeSegment(region)}). Parent stack '${safeSegment(stackName)}' lists '${safeSegment(logicalId)}' as an ${NESTED_STACK_RESOURCE_TYPE} row but no child state file exists at 'cdkd/${safeSegment(childStackName)}/${safeSegment(region)}/state.json'. The cdkd state tree is inconsistent — re-deploy the parent stack to refresh, or run 'cdkd state orphan ${safeSegment(stackName)}' and re-import.`);
76456
- if (childResult.state.region !== void 0 && childResult.state.region !== region) throw new Error(`cdkd state region mismatch: nested-child '${safeSegment(childStackName)}' has state.region='${safeSegment(childResult.state.region)}' but its parent '${safeSegment(stackName)}' is being walked against region='${safeSegment(region)}'. AWS does not support cross-region nested stacks; the state tree appears corrupt — re-deploy the parent stack to refresh.`);
76769
+ if (!childResult) throw new Error(`cdkd state is missing nested-child '${safeSegment(childStackName)}' (${safeSegment(region)}). Parent stack '${safeSegment(stackName)}' lists '${safeSegment(logicalId)}' as an ${NESTED_STACK_RESOURCE_TYPE} row but no child state file exists at 'cdkd/${safeSegment(childStackName)}/${safeSegment(region)}/state.json'. The cdkd state tree is inconsistent — re-deploy the parent stack to refresh, or drop the parent's record and re-import with: ${orphanCommandFor(stackName, region)}`);
76770
+ if (childResult.divergentBodyRegion !== void 0) throw new Error(`cdkd state region mismatch: nested-child '${safeSegment(childStackName)}' has state.region='${safeSegment(childResult.divergentBodyRegion)}' but its parent '${safeSegment(stackName)}' is being walked against region='${safeSegment(region)}'. AWS does not support cross-region nested stacks; the state tree appears corrupt — re-deploy the parent stack to refresh.`);
76457
76771
  nestedChildren.set(logicalId, await walkCdkdStateStackTree(childStackName, region, childResult.state, stateBackend));
76458
76772
  }
76459
76773
  return {
@@ -76534,7 +76848,7 @@ function flattenCdkdStateTreeRootFirst(tree) {
76534
76848
  */
76535
76849
  function cdkd2cfnStackName(cdkdStackName) {
76536
76850
  const cfnName = cdkdStackName.replace(/~/g, "-");
76537
- if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(cfnName)) throw new Error(`cdkd stack name '${cdkdStackName}' maps to CFn stack name '${cfnName}', which violates the CloudFormation stack-name constraint [a-zA-Z][-a-zA-Z0-9]* (must start with a letter; no '_', '.', '/', or other special characters). Supply an explicit name via --cfn-stack-name (root) or --cfn-child-stack-name '<cdkdName>=<cfnName>' (nested child).`);
76851
+ if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(cfnName)) throw new Error(`cdkd stack name '${safeSegment(cdkdStackName)}' maps to CFn stack name '${safeSegment(cfnName)}', which violates the CloudFormation stack-name constraint [a-zA-Z][-a-zA-Z0-9]* (must start with a letter; no '_', '.', '/', or other special characters). Supply an explicit name via --cfn-stack-name (root) or --cfn-child-stack-name '<cdkdName>=<cfnName>' (nested child).`);
76538
76852
  return cfnName;
76539
76853
  }
76540
76854
  /**
@@ -76560,7 +76874,7 @@ function parseCfnChildStackNameOverrides(values) {
76560
76874
  if (!cdkdName) throw new Error(`--cfn-child-stack-name '${raw}' has an empty cdkd stack name.`);
76561
76875
  if (!cfnName) throw new Error(`--cfn-child-stack-name '${raw}' has an empty CFn stack name.`);
76562
76876
  if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(cfnName)) throw new Error(`--cfn-child-stack-name '${raw}': CFn stack name '${cfnName}' must match [a-zA-Z][-a-zA-Z0-9]* (no '~', no '/', no '_', no '.').`);
76563
- if (out.has(cdkdName)) throw new Error(`--cfn-child-stack-name: duplicate override for cdkd stack '${cdkdName}'. Pass it once with the final CFn name.`);
76877
+ if (out.has(cdkdName)) throw new Error(`--cfn-child-stack-name: duplicate override for cdkd stack '${safeSegment(cdkdName)}'. Pass it once with the final CFn name.`);
76564
76878
  out.set(cdkdName, cfnName);
76565
76879
  }
76566
76880
  return out;
@@ -76646,7 +76960,7 @@ function indexNestedTemplatePaths(template, templateDir) {
76646
76960
  * orchestrator.
76647
76961
  */
76648
76962
  function buildPerStackImportNodes(rootStackName, rootTemplate, rootNestedTemplatePaths, rootTemplateFormat, tree) {
76649
- if (tree.stackName !== rootStackName) throw new Error(`buildPerStackImportNodes: tree root '${tree.stackName}' does not match expected root stack name '${rootStackName}'.`);
76963
+ if (tree.stackName !== rootStackName) throw new Error(`buildPerStackImportNodes: tree root '${safeSegment(tree.stackName)}' does not match expected root stack name '${safeSegment(rootStackName)}'.`);
76650
76964
  const out = /* @__PURE__ */ new Map();
76651
76965
  walk(tree, rootTemplate, rootNestedTemplatePaths, rootTemplateFormat);
76652
76966
  return out;
@@ -76660,7 +76974,7 @@ function buildPerStackImportNodes(rootStackName, rootTemplate, rootNestedTemplat
76660
76974
  });
76661
76975
  for (const [childLogicalId, childNode] of node.nestedChildren) {
76662
76976
  const childTemplatePath = nodeNestedTemplatePaths[childLogicalId];
76663
- if (!childTemplatePath) throw new Error(`cdkd export: nested-stack child '${childLogicalId}' under parent '${node.stackName}' has cdkd state but no Metadata['aws:asset:path'] in the parent template's '${childLogicalId}' row. The synth output and the cdkd state tree are out of sync — re-deploy the parent stack to refresh, or remove the cdkd state for the orphaned child via 'cdkd state orphan ${childNode.stackName}'.`);
76977
+ if (!childTemplatePath) throw new Error(`cdkd export: nested-stack child '${safeSegment(childLogicalId)}' under parent '${safeSegment(node.stackName)}' has cdkd state but no Metadata['aws:asset:path'] in the parent template's '${safeSegment(childLogicalId)}' row. The synth output and the cdkd state tree are out of sync — re-deploy the parent stack to refresh, or remove the cdkd state for the orphaned child with ${orphanCommandFor(childNode.stackName, childNode.region)}`);
76664
76978
  const { template: childTemplate, format: childFormat } = readNestedChildTemplateFile(childTemplatePath, childLogicalId);
76665
76979
  walk(childNode, childTemplate, indexNestedTemplatePaths(childTemplate, path.dirname(childTemplatePath)), childFormat);
76666
76980
  }
@@ -76712,7 +77026,7 @@ async function buildImportPlan(state, template, cfnClient, parentStackName, opti
76712
77026
  blocked.push({
76713
77027
  logicalId,
76714
77028
  resourceType,
76715
- reason: `template has AWS::CloudFormation::Stack row '${logicalId}' but cdkd state has no matching nested-stack entry on parent '${parentStackName}'. Re-deploy or re-import the parent stack to refresh state.`
77029
+ reason: `template has AWS::CloudFormation::Stack row '${logicalId}' but cdkd state has no matching nested-stack entry on parent '${safeSegment(parentStackName)}'. Re-deploy or re-import the parent stack to refresh state.`
76716
77030
  });
76717
77031
  continue;
76718
77032
  }
@@ -77405,13 +77719,13 @@ function reportDriftBaselineGaps(state, logger) {
77405
77719
  const missing = entries.filter(([, r]) => r.observedProperties === void 0);
77406
77720
  if (missing.length === 0) return;
77407
77721
  if (state.version !== void 0 && state.version < 3) {
77408
- logger.warn(`cdkd state schema is v${state.version} (pre-observedProperties). cdkd drift cannot reliably compare against AWS for this stack; the next \`cdk deploy\` after migration may surface spurious changes if AWS has drifted from the template. Run \`cdkd state refresh-observed ${state.stackName}\` (or any redeploy) before export to capture an AWS-current baseline.`);
77722
+ logger.warn(`cdkd state schema is v${state.version} (pre-observedProperties). cdkd drift cannot reliably compare against AWS for this stack; the next \`cdk deploy\` after migration may surface spurious changes if AWS has drifted from the template. Run \`cdkd state refresh-observed ${safeSegment(state.stackName)}\` (or any redeploy) before export to capture an AWS-current baseline.`);
77409
77723
  return;
77410
77724
  }
77411
77725
  const refused = missing.filter(([, r]) => r.observedBaselineRefused === true);
77412
77726
  const refreshable = missing.filter(([, r]) => r.observedBaselineRefused !== true);
77413
77727
  if (refreshable.length > 0) {
77414
- logger.warn(`${refreshable.length} of ${entries.length} resource(s) in cdkd state lack an AWS-current baseline (observedProperties). cdkd drift may produce false positives for them; the next \`cdk deploy\` after migration may surface unexpected changes. Run \`cdkd state refresh-observed ${state.stackName}\` to capture a baseline before export, then \`cdkd drift\` to verify the stack matches AWS.`);
77728
+ logger.warn(`${refreshable.length} of ${entries.length} resource(s) in cdkd state lack an AWS-current baseline (observedProperties). cdkd drift may produce false positives for them; the next \`cdk deploy\` after migration may surface unexpected changes. Run \`cdkd state refresh-observed ${safeSegment(state.stackName)}\` to capture a baseline before export, then \`cdkd drift\` to verify the stack matches AWS.`);
77415
77729
  for (const [logicalId] of refreshable.slice(0, 10)) logger.warn(` ${logicalId}`);
77416
77730
  if (refreshable.length > 10) logger.warn(` ... and ${refreshable.length - 10} more`);
77417
77731
  }
@@ -77954,12 +78268,12 @@ async function buildResolvedParametersPerStack(args) {
77954
78268
  for (const ref of flattenCdkdStateTreeRootFirst(args.tree)) {
77955
78269
  if (ref.stackName === args.rootStackName) continue;
77956
78270
  const node = nodeByName.get(ref.stackName);
77957
- if (!node) throw new Error(`buildResolvedParametersPerStack: tree node '${ref.stackName}' has no per-stack template/state entry. This is a cdkd bug.`);
78271
+ if (!node) throw new Error(`buildResolvedParametersPerStack: tree node '${safeSegment(ref.stackName)}' has no per-stack template/state entry. This is a cdkd bug.`);
77958
78272
  const parentLogicalId = node.state.parentLogicalId;
77959
78273
  const parentStackName = node.state.parentStack;
77960
- if (!parentLogicalId || !parentStackName) throw new Error(`buildResolvedParametersPerStack: child '${node.cdkdName}' state is missing parentLogicalId / parentStack (v6 fields). Re-deploy or re-import the parent stack.`);
78274
+ if (!parentLogicalId || !parentStackName) throw new Error(`buildResolvedParametersPerStack: child '${safeSegment(node.cdkdName)}' state is missing parentLogicalId / parentStack (v6 fields). Re-deploy or re-import the parent stack.`);
77961
78275
  const parentNode = nodeByName.get(parentStackName);
77962
- if (!parentNode) throw new Error(`buildResolvedParametersPerStack: child '${node.cdkdName}' references parent '${parentStackName}' which is not in the per-stack node list — tree shape is inconsistent.`);
78276
+ if (!parentNode) throw new Error(`buildResolvedParametersPerStack: child '${safeSegment(node.cdkdName)}' references parent '${safeSegment(parentStackName)}' which is not in the per-stack node list — tree shape is inconsistent.`);
77963
78277
  const parentParamValues = {};
77964
78278
  for (const p of paramsByCdkdName.get(parentStackName) ?? []) if (p.ParameterKey !== void 0) parentParamValues[p.ParameterKey] = p.ParameterValue;
77965
78279
  const parentResolverContext = {
@@ -78044,7 +78358,7 @@ async function runPerStackImportLoop(args) {
78044
78358
  };
78045
78359
  const nodesByCdkdName = buildPerStackImportNodes(rootStackName, rootTemplate, rootStackInfoNestedTemplates, rootTemplateFormat, tree);
78046
78360
  const leafFirst = flattenCdkdStateTreeLeafFirst(tree);
78047
- for (const n of leafFirst) if (!nodesByCdkdName.has(n.stackName)) throw new Error(`runPerStackImportLoop: missing per-stack template for '${n.stackName}' — tree node has cdkd state but no synth template was loaded. This is a cdkd bug.`);
78361
+ for (const n of leafFirst) if (!nodesByCdkdName.has(n.stackName)) throw new Error(`runPerStackImportLoop: missing per-stack template for '${safeSegment(n.stackName)}' — tree node has cdkd state but no synth template was loaded. This is a cdkd bug.`);
78048
78362
  const perStackPlans = [];
78049
78363
  for (const node of leafFirst) {
78050
78364
  const meta = nodesByCdkdName.get(node.stackName);
@@ -78055,7 +78369,7 @@ async function runPerStackImportLoop(args) {
78055
78369
  });
78056
78370
  if (plan.blocked.length > 0) {
78057
78371
  const lines = groupBlockedReasons(plan.blocked);
78058
- throw new Error(`Stack '${meta.cdkdStackName}' has ${new Set(plan.blocked.map((b) => b.logicalId)).size} resource(s) that block migration:\n${lines.join("\n")}`);
78372
+ throw new Error(`Stack '${safeSegment(meta.cdkdStackName)}' has ${new Set(plan.blocked.map((b) => b.logicalId)).size} resource(s) that block migration:\n${lines.join("\n")}`);
78059
78373
  }
78060
78374
  perStackPlans.push({
78061
78375
  cdkdName: meta.cdkdStackName,
@@ -78074,7 +78388,7 @@ async function runPerStackImportLoop(args) {
78074
78388
  const totalRecreate = perStackPlans.reduce((acc, p) => acc + p.recreateBeforePhase2.length, 0);
78075
78389
  logger.info("");
78076
78390
  logger.info(`Migrating cdkd nested-stack tree rooted at '${rootStackName}' → CloudFormation (${perStackPlans.length} stack(s), leaf-first):`);
78077
- for (const plan of perStackPlans) logger.info(` [${plan.cdkdName}] → CFn stack '${plan.cfnName}': ${plan.phase1Imports.length} leaf import(s)` + (plan.nestedStackRows.length > 0 ? `, ${plan.nestedStackRows.length} nested-child adoption(s)` : "") + (plan.phase2Creates.length > 0 ? `, ${plan.phase2Creates.length} phase-2 CREATE(s)` : "") + (plan.recreateBeforePhase2.length > 0 ? `, ${plan.recreateBeforePhase2.length} pre-delete + re-CREATE` : ""));
78391
+ for (const plan of perStackPlans) logger.info(` [${safeSegment(plan.cdkdName)}] → CFn stack '${safeSegment(plan.cfnName)}': ${plan.phase1Imports.length} leaf import(s)` + (plan.nestedStackRows.length > 0 ? `, ${plan.nestedStackRows.length} nested-child adoption(s)` : "") + (plan.phase2Creates.length > 0 ? `, ${plan.phase2Creates.length} phase-2 CREATE(s)` : "") + (plan.recreateBeforePhase2.length > 0 ? `, ${plan.recreateBeforePhase2.length} pre-delete + re-CREATE` : ""));
78078
78392
  logger.info("");
78079
78393
  if (totalPhase2Creates > 0 && !options.includeNonImportable) {
78080
78394
  if (options.dryRun) {
@@ -78098,7 +78412,7 @@ async function runPerStackImportLoop(args) {
78098
78412
  if (!options.yes) {
78099
78413
  const phase2Note = totalPhase2Creates > 0 ? ` Phase 2 will CREATE ${totalPhase2Creates} non-importable resource(s) across the tree.` : "";
78100
78414
  const recreateNote = totalRecreate > 0 ? ` cdkd will also pre-DELETE + re-CREATE ${totalRecreate} IMPORT-unsupported resource(s) (brief unavailability window per resource).` : "";
78101
- if (!await confirmPrompt$3(`Create ${perStackPlans.length} CloudFormation stack(s) by importing the cdkd nested-stack tree rooted at '${rootStackName}' (${rootRegion}) — leaf-first, per-stack IMPORT loop (#464 design §4.3).` + phase2Note + recreateNote + " AWS resources are unchanged. cdkd state for every adopted stack will be deleted on success.")) {
78415
+ if (!await confirmPrompt$3(`Create ${perStackPlans.length} CloudFormation stack(s) by importing the cdkd nested-stack tree rooted at '${rootStackName}' (${safeSegment(rootRegion)}) — leaf-first, per-stack IMPORT loop (#464 design §4.3).` + phase2Note + recreateNote + " AWS resources are unchanged. cdkd state for every adopted stack will be deleted on success.")) {
78102
78416
  logger.info("Migration cancelled. cdkd state and CloudFormation are unchanged.");
78103
78417
  return {
78104
78418
  outcome: "cancelled",
@@ -78144,11 +78458,11 @@ async function runPerStackImportLoop(args) {
78144
78458
  resolver: paramResolver,
78145
78459
  stateBackend: deps.stateBackend
78146
78460
  }));
78147
- for (const [cdkdName, skipped] of sessionIntrinsicSkipped) logger.warn(` Child '${cdkdName}': could not resolve intrinsic-valued Parameter(s) ${skipped.join(", ")} at IMPORT time. The child template's Parameter Default values must cover these — otherwise CFn will reject the IMPORT.`);
78461
+ for (const [cdkdName, skipped] of sessionIntrinsicSkipped) logger.warn(` Child '${safeSegment(cdkdName)}': could not resolve intrinsic-valued Parameter(s) ${skipped.join(", ")} at IMPORT time. The child template's Parameter Default values must cover these — otherwise CFn will reject the IMPORT.`);
78148
78462
  try {
78149
78463
  for (let i = 0; i < perStackPlans.length; i++) {
78150
78464
  const plan = perStackPlans[i];
78151
- logger.info(`[${i + 1}/${perStackPlans.length}] Importing cdkd stack '${plan.cdkdName}' → CFn stack '${plan.cfnName}' (${plan.phase1Imports.length} leaf, ${plan.nestedStackRows.length} nested-child adoption)`);
78465
+ logger.info(`[${i + 1}/${perStackPlans.length}] Importing cdkd stack '${safeSegment(plan.cdkdName)}' → CFn stack '${plan.cfnName}' (${plan.phase1Imports.length} leaf, ${plan.nestedStackRows.length} nested-child adoption)`);
78152
78466
  const stackParameters = paramsByCdkdName.get(plan.cdkdName) ?? [];
78153
78467
  const phase1ATemplate = filterTemplateForImport(plan.template, plan.phase1Imports);
78154
78468
  const phase1AResources = plan.phase1Imports.map((entry) => ({
@@ -78157,13 +78471,13 @@ async function runPerStackImportLoop(args) {
78157
78471
  ResourceIdentifier: entry.resourceIdentifier
78158
78472
  }));
78159
78473
  const injectedDeletion = injectDeletionPolicyForImport(phase1ATemplate);
78160
- if (injectedDeletion > 0) logger.info(` Injected DeletionPolicy: Delete on ${injectedDeletion} resource(s) in '${plan.cdkdName}' (required by CFn IMPORT; matches CDK/CFn default).`);
78474
+ if (injectedDeletion > 0) logger.info(` Injected DeletionPolicy: Delete on ${injectedDeletion} resource(s) in '${safeSegment(plan.cdkdName)}' (required by CFn IMPORT; matches CDK/CFn default).`);
78161
78475
  try {
78162
78476
  await submitImportChangeSet(deps.cfnClient, plan.cfnName, phase1ATemplate, phase1AResources, stackParameters, plan.templateFormat, deps.uploadOpts);
78163
78477
  } catch (err) {
78164
- const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${s.cdkdStackName} → ${s.cfnStackName}`).join(", ") : "(none)";
78165
- const remainingSummary = perStackPlans.slice(i).map((p) => p.cdkdName).join(", ");
78166
- throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${plan.cdkdName}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run 'cdkd export ${rootStackName}' — already-imported children will be adopted as nested references on retry. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
78478
+ const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${safeSegment(s.cdkdStackName)} → ${safeSegment(s.cfnStackName)}`).join(", ") : "(none)";
78479
+ const remainingSummary = perStackPlans.slice(i).map((p) => safeSegment(p.cdkdName)).join(", ");
78480
+ throw new Error(`Phase 1A IMPORT changeset failed for cdkd stack '${safeSegment(plan.cdkdName)}' (CFn name '${plan.cfnName}'). Stacks imported successfully so far: ${importedSummary}. Stacks not yet imported (cdkd state preserved): ${remainingSummary}. After resolving the underlying cause, re-run 'cdkd export ${rootStackName}' — already-imported children will be adopted as nested references on retry. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
78167
78481
  }
78168
78482
  const cfnArn = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: plan.cfnName }))).Stacks?.[0]?.StackId;
78169
78483
  if (!cfnArn) throw new Error(`runPerStackImportLoop: DescribeStacks returned no StackId for '${plan.cfnName}' immediately after Phase 1A IMPORT — AWS may be in an unexpected state.`);
@@ -78185,7 +78499,7 @@ async function runPerStackImportLoop(args) {
78185
78499
  const phase1BResources = [];
78186
78500
  for (const row of plan.nestedStackRows) {
78187
78501
  const childArn = cfnArnByCdkdName.get(row.childStackName);
78188
- if (!childArn) throw new Error(`runPerStackImportLoop: nested-stack child '${row.childStackName}' has no recorded CFn ARN when processing parent '${plan.cdkdName}'. Leaf-first iteration order should have IMPORTed the child first — this is a cdkd bug.`);
78502
+ if (!childArn) throw new Error(`runPerStackImportLoop: nested-stack child '${safeSegment(row.childStackName)}' has no recorded CFn ARN when processing parent '${safeSegment(plan.cdkdName)}'. Leaf-first iteration order should have IMPORTed the child first — this is a cdkd bug.`);
78189
78503
  const childCfnName = cfnStackNameOf(row.childStackName);
78190
78504
  const childTemplateBody = await fetchCfnStackTemplate(deps.cfnClient, childCfnName);
78191
78505
  const uploaded = await uploadCfnTemplate({
@@ -78198,7 +78512,7 @@ async function runPerStackImportLoop(args) {
78198
78512
  uploadCleanups.push(uploaded.cleanup);
78199
78513
  const childActualTags = (await deps.cfnClient.send(new DescribeStacksCommand({ StackName: childCfnName }))).Stacks?.[0]?.Tags ?? [];
78200
78514
  const originalRow = getResourceFromTemplate(plan.template, row.logicalId);
78201
- if (!originalRow) throw new Error(`runPerStackImportLoop: parent template for '${plan.cdkdName}' has no resource '${row.logicalId}'. State and template are out of sync.`);
78515
+ if (!originalRow) throw new Error(`runPerStackImportLoop: parent template for '${safeSegment(plan.cdkdName)}' has no resource '${row.logicalId}'. State and template are out of sync.`);
78202
78516
  const rewrittenRow = injectRetainAndRewriteTemplateUrl(originalRow, uploaded.url, childActualTags);
78203
78517
  phase1BTemplate["Resources"][row.logicalId] = rewrittenRow;
78204
78518
  rewrittenNestedRows.set(row.logicalId, rewrittenRow);
@@ -78211,8 +78525,8 @@ async function runPerStackImportLoop(args) {
78211
78525
  try {
78212
78526
  await submitImportChangeSet(deps.cfnClient, plan.cfnName, phase1BTemplate, phase1BResources, stackParameters, plan.templateFormat, deps.uploadOpts);
78213
78527
  } catch (err) {
78214
- const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${s.cdkdStackName} → ${s.cfnStackName}`).join(", ") : "(none)";
78215
- throw new Error(`Phase 1B (nested-child adoption) IMPORT changeset failed for parent '${plan.cdkdName}' (CFn name '${plan.cfnName}'). The parent CFn stack exists with its ${plan.phase1Imports.length} leaf resource(s); ${plan.nestedStackRows.length} nested-child adoption(s) did NOT complete. Stacks IMPORTed so far (each is a standalone CFn stack): ${importedSummary}. cdkd state for every stack in the tree is preserved. To recover: (1) fix the underlying cause (typically a template-match validation error per AWS-docs "Nested stack import validation"); (2) clear cdkd state for the migrated stacks via 'cdkd state orphan <stack>' (do NOT 'cdkd destroy' — that would tear down the live AWS resources); (3) re-attempt the parent-side nested adoption manually via the AWS console or CLI per the AWS docs procedure. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
78528
+ const importedSummary = importedStacks.length > 0 ? importedStacks.map((s) => `${safeSegment(s.cdkdStackName)} → ${safeSegment(s.cfnStackName)}`).join(", ") : "(none)";
78529
+ throw new Error(`Phase 1B (nested-child adoption) IMPORT changeset failed for parent '${safeSegment(plan.cdkdName)}' (CFn name '${safeSegment(plan.cfnName)}'). The parent CFn stack exists with its ${plan.phase1Imports.length} leaf resource(s); ${plan.nestedStackRows.length} nested-child adoption(s) did NOT complete. Stacks IMPORTed so far (each is a standalone CFn stack): ${importedSummary}. cdkd state for every stack in the tree is preserved. To recover: (1) fix the underlying cause (typically a template-match validation error per AWS-docs "Nested stack import validation"); (2) clear cdkd state for the migrated stacks via 'cdkd state orphan <stack>' (do NOT 'cdkd destroy' — that would tear down the live AWS resources); (3) re-attempt the parent-side nested adoption manually via the AWS console or CLI per the AWS docs procedure. Cause: ${err instanceof Error ? err.message : String(err)}`, { cause: err instanceof Error ? err : void 0 });
78216
78530
  }
78217
78531
  logger.info(` ✓ Phase 1B: parent '${plan.cfnName}' adopted ${plan.nestedStackRows.length} nested child(ren) via UPDATE-IMPORT.`);
78218
78532
  if (plan.cdkdName !== rootStackName) {
@@ -78222,7 +78536,7 @@ async function runPerStackImportLoop(args) {
78222
78536
  }
78223
78537
  if (plan.recreateBeforePhase2.length > 0) for (const entry of plan.recreateBeforePhase2) {
78224
78538
  const handler = PRE_DELETE_HANDLERS[entry.resourceType];
78225
- if (!handler) throw new Error(`No pre-delete handler registered for ${entry.resourceType} (${entry.logicalId}) in stack '${plan.cdkdName}'. This is a cdkd bug — the resource is in IMPORT_UNSUPPORTED_RECREATABLE_TYPES but lacks a PRE_DELETE_HANDLERS entry.`);
78539
+ if (!handler) throw new Error(`No pre-delete handler registered for ${entry.resourceType} (${entry.logicalId}) in stack '${safeSegment(plan.cdkdName)}'. This is a cdkd bug — the resource is in IMPORT_UNSUPPORTED_RECREATABLE_TYPES but lacks a PRE_DELETE_HANDLERS entry.`);
78226
78540
  logger.info(` Pre-deleting AWS resource for ${entry.logicalId} (${entry.resourceType}) so CFn can re-CREATE in phase 2...`);
78227
78541
  await handler(entry);
78228
78542
  logger.info(` ✓ deleted ${entry.physicalId}`);
@@ -78241,21 +78555,21 @@ async function runPerStackImportLoop(args) {
78241
78555
  const stateDeletionFailures = [];
78242
78556
  for (const node of leafFirst) try {
78243
78557
  await deps.stateBackend.deleteState(node.stackName, node.region);
78244
- logger.info(`cdkd state for '${node.stackName}' (${node.region}) removed.`);
78558
+ logger.info(`cdkd state for '${safeSegment(node.stackName)}' (${safeSegment(node.region)}) removed.`);
78245
78559
  } catch (err) {
78246
78560
  stateDeletionFailures.push({
78247
78561
  stackName: node.stackName,
78248
78562
  region: node.region,
78249
78563
  reason: err instanceof Error ? err.message : String(err)
78250
78564
  });
78251
- logger.warn(`Failed to delete cdkd state for '${node.stackName}' (${node.region}): ${err instanceof Error ? err.message : String(err)}. The stack IS CFn-managed; clean up with 'cdkd state orphan ${node.stackName}'.`);
78565
+ logger.warn(`Failed to delete cdkd state for '${safeSegment(node.stackName)}' (${safeSegment(node.region)}): ${safeDetail(err)}. The stack IS CFn-managed; clean up with: ${orphanCommandFor(node.stackName, node.region)}`);
78252
78566
  }
78253
78567
  if (stateDeletionFailures.length > 0) {
78254
- const lines = stateDeletionFailures.map((f) => ` - cdkd/${f.stackName}/${f.region}/state.json: ${f.reason}`).join("\n");
78255
- throw new Error(`${stateDeletionFailures.length} cdkd state record(s) could not be deleted after a successful per-stack IMPORT loop. Every stack in the tree IS CFn-managed (the migration succeeded AWS-side); orphan state remains under:\n${lines}\nRecover with 'cdkd state orphan <stack>' per record.`);
78568
+ const lines = stateDeletionFailures.map((f) => ` - cdkd/${safeSegment(f.stackName)}/${safeSegment(f.region)}/state.json: ${safeDetail(f.reason)}`).join("\n");
78569
+ throw new Error(`${stateDeletionFailures.length} cdkd state record(s) could not be deleted after a successful per-stack IMPORT loop. Every stack in the tree IS CFn-managed (the migration succeeded AWS-side); orphan state remains under:\n${lines}\nRecover with 'cdkd state orphan <stack> --stack-region <region>' per record.`);
78256
78570
  }
78257
78571
  if (sessionIntrinsicSkipped.size > 0) {
78258
- const detail = [...sessionIntrinsicSkipped.entries()].map(([cdkdName, params]) => `${cdkdName} (${params.join(", ")})`).join("; ");
78572
+ const detail = [...sessionIntrinsicSkipped.entries()].map(([cdkdName, params]) => `${safeSegment(cdkdName)} (${params.join(", ")})`).join("; ");
78259
78573
  logger.warn(`${sessionIntrinsicSkipped.size} stack(s) had intrinsic-valued Parameter(s) that cdkd could not resolve at IMPORT time: ${detail}. Verify each child template's Parameter Default values cover these before the first 'cdk deploy'.`);
78260
78574
  }
78261
78575
  return {
@@ -78269,7 +78583,7 @@ async function runPerStackImportLoop(args) {
78269
78583
  for (let i = acquiredChildLocks.length - 1; i >= 0; i--) {
78270
78584
  const lock = acquiredChildLocks[i];
78271
78585
  await deps.lockManager.releaseLock(lock.stackName, lock.region).catch((err) => {
78272
- logger.warn(`Failed to release lock for '${lock.stackName}' (${lock.region}): ${err instanceof Error ? err.message : String(err)}`);
78586
+ logger.warn(`Failed to release lock for '${safeSegment(lock.stackName)}' (${safeSegment(lock.region)}): ${safeDetail(err)}`);
78273
78587
  });
78274
78588
  }
78275
78589
  }
@@ -79679,9 +79993,29 @@ function createStateOrphanCommand() {
79679
79993
  * state record. AWS resources are left intact.
79680
79994
  *
79681
79995
  * Region scoping: when a stack name has multiple state records spread across
79682
- * regions (PR 1 territory), `--region` selects one. With the current single-
79683
- * region-per-name layout the flag still works — it sets the AWS clients'
79684
- * region and refuses to proceed if the loaded state.region disagrees.
79996
+ * regions (PR 1 territory), `--stack-region` selects one — it picks WHICH
79997
+ * record is loaded, because the region is part of the S3 key.
79998
+ *
79999
+ * What it does with a record whose BODY names a different region than that key
80000
+ * is two things, neither of which this comment described until issue
80001
+ * [#3328](https://github.com/go-to-k/cdkd/issues/3328) — it claimed a refusal
80002
+ * on `state.region` that had never existed anywhere in the command, while the
80003
+ * measured behaviour was the opposite: a planted record at
80004
+ * `.../us-east-1/state.json` carrying `"region": "eu-west-1"` locked, saved
80005
+ * and deleted against `eu-west-1` and reported `✓ State deleted` while the
80006
+ * us-east-1 key survived.
80007
+ *
80008
+ * 1. `S3StateBackend.getState` normalizes the loaded record's `region` to the
80009
+ * KEY's and WARNS, so this command — which hands `stateResult.state`
80010
+ * straight to `runDestroyForStack` — acts on the region it was pointed at.
80011
+ * The read itself refuses NOTHING; `adoptKeyRegion` carries why, and it is
80012
+ * what keeps `state show` / `state list` / `state orphan` working on such a
80013
+ * record.
80014
+ * 2. The DESTROY refuses it when the record still lists resources
80015
+ * (`refuseDivergentRecordRegionForDestroy`, reached through
80016
+ * `runDestroyForStack`), because which region those resources are in is
80017
+ * exactly what the divergence makes undecidable. A resource-less record is
80018
+ * not refused, so cleaning one up still works.
79685
80019
  */
79686
80020
  async function stateDestroyCommand(stackArgs, options) {
79687
80021
  const logger = getLogger();
@@ -79828,6 +80162,7 @@ async function stateDestroyCommand(stackArgs, options) {
79828
80162
  providerRegistry,
79829
80163
  baseAwsClients: setup.awsClients,
79830
80164
  baseRegion: setup.region,
80165
+ ...stateResult.divergentBodyRegion !== void 0 && { divergentBodyRegion: stateResult.divergentBodyRegion },
79831
80166
  ...options.profile && { profile: options.profile },
79832
80167
  stateBucket: setup.bucket,
79833
80168
  statePrefix: options.statePrefix,
@@ -90003,13 +90338,12 @@ function readEnvOverridesFile$3(filePath) {
90003
90338
  * which drives the `--role-arn` cases without booting the API server.
90004
90339
  */
90005
90340
  function forwardAwsEnv$2(env) {
90006
- for (const key of [
90007
- "AWS_ACCESS_KEY_ID",
90008
- "AWS_SECRET_ACCESS_KEY",
90009
- "AWS_SESSION_TOKEN",
90341
+ const passThrough = [
90342
+ ...AWS_CREDENTIAL_ENV_KEYS,
90010
90343
  "AWS_REGION",
90011
90344
  "AWS_DEFAULT_REGION"
90012
- ]) {
90345
+ ];
90346
+ for (const key of passThrough) {
90013
90347
  const value = process.env[key];
90014
90348
  if (value !== void 0) env[key] = value;
90015
90349
  }
@@ -92484,6 +92818,36 @@ async function buildSigV4HeadersIfRequested(options, resolved, loaded, host, por
92484
92818
  return headers;
92485
92819
  }
92486
92820
  /**
92821
+ * The `--sigv4` refusal, as a pure function of the two facts that decide its
92822
+ * wording (issue [#3250](https://github.com/go-to-k/cdkd/issues/3250) item 5).
92823
+ *
92824
+ * NAMING THE EXCLUSION. On a `--role-arn` run over a caller with no static
92825
+ * triple, `AWS_ACCESS_KEY_ID` IS set in this process — cdkd set it, to the
92826
+ * role — so "set AWS_ACCESS_KEY_ID" alone reads as a bug report about cdkd
92827
+ * rather than as the refusal it is. The message has to say which value it
92828
+ * declines and why.
92829
+ *
92830
+ * THE REMEDY ORDER DIFFERS PER BRANCH, deliberately. The assumed-role branch
92831
+ * fires precisely when the caller has no static pair BY DESIGN — SSO, an
92832
+ * instance role, a container role — so leading with "mint an access key pair"
92833
+ * tells the one user who should not do that to do it first. The role-less
92834
+ * branch is the opposite: there the pair genuinely is the simplest answer.
92835
+ * `--profile` is dropped from either list when one was already passed and did
92836
+ * not produce a usable pair — the same already-followed-advice defect, one arm
92837
+ * over.
92838
+ *
92839
+ * PURE, and separate from its caller, for a reason the caller cannot give: the
92840
+ * `--profile` arm of {@link resolveHostCredentialsForSigV4} transacts with AWS,
92841
+ * so the unit suite's network fence refuses every route that reaches this throw
92842
+ * with `options.profile` set. As a helper, all four combinations are testable
92843
+ * with no network at all.
92844
+ */
92845
+ function sigv4NoCredentialsRefusal(args) {
92846
+ const exportPair = `set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY in your shell before running cdkd (the snapshot cdkd puts back is taken before it assumes the role, so exporting them afterwards is too late)`;
92847
+ const profileRemedy = args.profileAlreadyPassed ? "" : `pass --profile <name>, `;
92848
+ return new CdkdError(args.assumedRolePublished ? `--sigv4: no AWS credentials available to sign the request as YOU. AWS_ACCESS_KEY_ID is set in this process, but cdkd set it: it holds the --role-arn assumed role, and this signature is what the emulated agent sees as its INVOKER, so cdkd deliberately will not sign as that role. Your own credentials carried no static triple before the assume — an SSO / IAM Identity Center, EC2 instance or ECS container chain, or a profile you exported rather than passed — so there is nothing to put back. To fix: ${profileRemedy}pass --assume-role <arn>, or ${exportPair}.` : `--sigv4: no AWS credentials available to sign the request. To fix: ${exportPair}, ${profileRemedy}or pass --assume-role <arn>.`, "LOCAL_INVOKE_AGENTCORE_SIGV4_NO_CREDENTIALS");
92849
+ }
92850
+ /**
92487
92851
  * Resolve credentials for host-side SigV4 signing. Precedence:
92488
92852
  * 1. `--assume-role` → STS temp creds (warn + fall through on STS failure);
92489
92853
  * 2. `--profile` → profile creds (sessionToken when the profile carries one);
@@ -92526,7 +92890,10 @@ async function resolveHostCredentialsForSigV4(options, resolved, loaded, region)
92526
92890
  secretAccessKey: shellCredentials.secretAccessKey,
92527
92891
  ...shellCredentials.sessionToken && { sessionToken: shellCredentials.sessionToken }
92528
92892
  };
92529
- throw new CdkdError("--sigv4: no AWS credentials available to sign the request. Set AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY, pass --profile <name>, or pass --assume-role <arn>.", "LOCAL_INVOKE_AGENTCORE_SIGV4_NO_CREDENTIALS");
92893
+ throw sigv4NoCredentialsRefusal({
92894
+ assumedRolePublished: envCredentialsAreAssumedRole(),
92895
+ profileAlreadyPassed: options.profile !== void 0
92896
+ });
92530
92897
  }
92531
92898
  /**
92532
92899
  * Acquire the agent image. A CODE artifact (managed runtime) is built from
@@ -92967,9 +93334,7 @@ function platformToArchitecture(platform) {
92967
93334
  */
92968
93335
  function forwardAwsEnv$1(env) {
92969
93336
  const passThrough = [
92970
- "AWS_ACCESS_KEY_ID",
92971
- "AWS_SECRET_ACCESS_KEY",
92972
- "AWS_SESSION_TOKEN",
93337
+ ...AWS_CREDENTIAL_ENV_KEYS,
92973
93338
  "AWS_REGION",
92974
93339
  "AWS_DEFAULT_REGION"
92975
93340
  ];
@@ -94145,9 +94510,7 @@ async function assumeLambdaExecutionRole(roleArn, region) {
94145
94510
  */
94146
94511
  function forwardAwsEnv(env) {
94147
94512
  const passThrough = [
94148
- "AWS_ACCESS_KEY_ID",
94149
- "AWS_SECRET_ACCESS_KEY",
94150
- "AWS_SESSION_TOKEN",
94513
+ ...AWS_CREDENTIAL_ENV_KEYS,
94151
94514
  "AWS_REGION",
94152
94515
  "AWS_DEFAULT_REGION"
94153
94516
  ];
@@ -94395,4 +94758,4 @@ function buildProgram() {
94395
94758
 
94396
94759
  //#endregion
94397
94760
  export { buildProgram };
94398
- //# sourceMappingURL=program-DaoJ7P4H.js.map
94761
+ //# sourceMappingURL=program-BgDlhE-S.js.map