@go-to-k/cdkd 0.290.34 → 0.290.36

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 isExportAliasCollision, $n as maskSecretsInText, $r as stripControlChars, $t as withSharedDrainBudget, A as makeCanonicalizePropertiesFn, Ai as AwsClients, An as refuseMalformedResourcesForDestroy, Ar as AssetModeResolver, At as getAccountInfo, B as isWarmThroughputDecrease, Bi as DynamicReferenceRegionAmbiguousError, Bn as TemplateParser, Br as buildDenyExternalAccessPolicy, Bt as assertRegionMatch, C as PRE_DELETE_SNAPSHOT_TYPES, Ci as PARTITION_TABLE, Cn as malformedStateRefusalMessage, Cr as AssetPublisher, Ct as disableInstanceApiTermination, D as isFinalSnapshotError, Di as processStackMessages, Dn as refuseMalformedOutputs, Dr as createAssetRedirectResolver, Dt as carriesFetchableDynamicReference, E as createPreDeleteFinalSnapshot, Ei as AssemblyReader, En as refuseMalformedNestedChildOutputs, Er as buildAssetRedirectMap, Et as carriesDynamicReference, F as isStatefulRecreateTargetForReplace, Fi as CdkdError, Fn as findActionableSilentDrops, Fr as isCrossRegionRedirect, Ft as refStateLookupFromResource, G as cyan, Gi as MalformedProducerRecordRefusalError, Gn as carriesSecretMask, Gr as dockerSpawnEnvWithSensitive, Gt as configStringRefusal, H as toFiniteNumber, Hi as LocalInvokeBuildError$1, Hn as STATE_SOURCED_CROSS_GENERATION_RULES, Hr as describeDockerCapturedOutput, Ht as coerceCfnInteger, I as isStatefulRecreateTargetSync, In as findSilentDropProperties, Ir as parseBootstrapMarker, It as isSettledInstanceState, J as red, Ji as ProvisioningError, Jn as errorCauseChain, Jr as partitionSensitiveEnv, Jt as requireConfigArray, K as gray, Ki as NestedStackChildDirectDestroyError, Kn as createSecretMasker, Kr as formatDockerLoginError, Kt as readConfigString, L as renderStatefulReason, Li as CrossAccountSecretRefusalError, Ln as describeTypeWithThrottleRetry, Lr as readBootstrapMarkerBody, Lt as WAFv2WebACLProvider, M as makeSiblingClaimReader, Mi as resetAwsClients, Mn as repairMalformedOutputsForReadOnly, Mr as assertAssetBucketRegion, Mt as isNamelessDynamicReferenceError, N as planOrphanAdoption, Ni as setAwsClients, Nn as repairMalformedResourcePropertiesForReadOnly, Nr as ensureAssetStorage, Nt as isUnboundTemplateParameter, O as refusesFinalSnapshot, On as refuseMalformedOutputsForDestroy, Or as loadPublishableAssetManifest, Ot as cfnRefValueFromPhysicalId, P as MULTI_REGION_RECREATE_BLOCKED_TYPES, Pn as repairMalformedResourcesForReadOnly, Pr as getBootstrapMarkerKey, Pt as parameterTypeMayLoseSecretIdentity, Q as exportAliasCollisionScrubWarning, Qi as StackTerminationProtectionError, Qn as maskSecretsInError, Qr as escapeRegExp, Qt as producerRegionsFromState, R as WARM_THROUGHPUT_MEMBERS, Rn as withRetry, Rr as validateAssetBucketName, Rt as normalizeAwsTagsToCfn, S as ATOMIC_FINAL_SNAPSHOT_TYPES, Si as expectedOwnerParam, Sn as malformedResourcesWarning, Sr as shouldRetainResource, St as deleteSkipReason, T as ccRoutedFinalSnapshotError, Ti as derivePartitionAndUrlSuffix, Tn as refuseDivergentRecordRegionForDestroy, Tr as WorkGraph, Tt as IntrinsicFunctionResolver, U as formatResourceLine, Ui as LocalStartServiceError, Un as STATE_SOURCED_READBACK_RULES, Ur as describeDockerExecFailure, Ut as configBooleanRefusal, V as toCfnInteger, Vi as IntrinsicResolutionRefusalError, Vn as STATE_SOURCED_BASELINE_RULES, Vr as buildDockerImage, Vt as coerceCfnBoolean, W as bold, Wn as TEMPLATE_SOURCED_RULES, Wr as describeDockerFailure, Wt as configIntegerRefusal, X as collectDeclaredOutputNames, Xi as ResourceUpdateNotSupportedError, Xn as isSingleDynamicReferenceToken, Xr as runDockerForeground, Xt as requireConfigString, Y as yellow, Yi as ResourceTimeoutError, Yn as identityKeyFor, Yr as redactDockerArgvValues, Yt as requireConfigObject, Z as collectPublishedOutputNames, Zi as StackHasActiveImportsError, Zn as markSameGenerationBag, Zr as runDockerStreaming, Zt as classifyReplaySecretRegion, _ as bagHoldsSecretExpression, _i as MIGRATE_TMP_PREFIX, _n as malformedLocalOutputsWarning, _r as importableOutputKeys, _t as CloudControlProvider, a as DeploymentEventsStore, aa as isMarkedNonRetryable, ai as getLegacyStateBucketName, an as applyRoleArnIfSet, ar as S3StateBackend, at as collectInlinePolicyNamesManagedBySiblings, b as mergeNoChangeOutputs, bi as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, bn as malformedRenderedContainersWarning, br as orphansAfterRollback, bt as UNSPECIFIED_SKIP_REASON, c as replayFailedOperations, ca as isTransientServerError, ci as resolveCaptureObservedState, cn as callerEnvCredentials, cr as buildForceUnlockCommand, ct as wouldReturnToSdkProvider, d as updatePartialMessage, da as retryClassificationText, di as resolveStateBucketWithDefaultAndSource, dn as INTRINSIC_KEYS, dr as formatLockExpiry, dt as maskerOrIdentity, ea as SynthesisError, ei as AssetManifestLoader, en as s3BucketArn, er as recordMaskOnlyValue, et as secretBearing, f as updatePartialReason, fa as describeAwsFailure, fi as resolveUseCdkBootstrapAssets, fn as STATE_RESOURCES_MALFORMED, fr as shellQuote, ft as beginCommandInterruptScope, g as computeImplicitDeleteEdges, gi as CFN_TEMPLATE_URL_LIMIT, gn as malformedExportSourceWarning, gr as hasReadableExportSet, gt as startInterruptWatch, h as IMPLICIT_DELETE_DEPENDENCIES, hi as CFN_TEMPLATE_BODY_LIMIT, hn as malformedExportNamesWarning, hr as exportNamesCarriedFrom, ht as isInterruptedWaitError, i as DeploymentEventsReader, ia as withErrorHandling, ii as getDefaultStateBucketName, in as s3BucketWebsiteUrl, ir as LockManager, it as IAMRoleProvider, j as extractDeploymentEventError, ji as getAwsClients, jn as refuseMalformedState, jr as BOOTSTRAP_MARKER_PREFIX, jt as isImpossibleEmptyStoredAttribute, k as unsupportedFinalSnapshotError, ki as resolveBucketRegion, kn as refuseMalformedResourcePropertiesForOrphan, kr as rewriteTemplateAssetReferences, kt as coerceParameterTypedValue, l as replayRollback, la as markNonRetryable, li as resolveSkipPrefix, ln as envCredentialsAreAssumedRole, lr as buildLockContentionMessage, lt as createMaskedRetryLogger, m as maskingRetryLogger, mi as warnDeprecatedNoPrefixCliFlag, mn as hasReadableResources, mr as DEFAULT_STATE_PREFIX, mt as interruptWatchListenerCount, n as DEFAULT_RESOURCE_WARN_AFTER_MS, ni as Synthesizer, nn as s3BucketDualStackDomainName, nr as redactSecretsForState, nt as secretSafeKeyDisplay, o as planFailedOps, oa as isRetryableTransientError, oi as resolveApp, on as AWS_CREDENTIAL_ENV_KEYS, or as rebuildClientForBucketRegion, ot as clearOnUpdateRemoval, p as withResourceDeadline, pa as safeStringify, pi as stateBucketExistenceConfirmed, pn as hasReadableOutputs, pr as CUSTOM_RESOURCE_RESPONSE_PREFIX, pt as endCommandInterruptScope, q as green, qi as PartialFailureError, qn as dynamicReferenceTokens, qr as getDockerCmd, qt as replayWarn, r as DeployEngine, ra as normalizeAwsError, ri as synthesisStatusMessage, rn as s3BucketRegionalDomainName, rr as scrubResourceRecord, rt as getCurrentResourceSecrets, s as planRollback, sa as isThrottlingError, si as resolveAutoAssetStorage, sn as applyCallerIdentityCredentials, sr as UNREPRODUCIBLE_LOCK_CLAUSE, st as ProviderRegistry, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as getDockerImageBySourceHash, tn as s3BucketDomainName, tr as recoverMaskedOutput, tt as secretBearingStateKeyWarning, u as resolveReplacementOldType, ua as markRedactedCause, ui as resolveStateBucketWithDefault, un as DiffCalculator, ur as forceQuitRecoveryClause, ut as maskDeep, v as isSecretBearingReferenceString, vi as findLargeInlineResources, vn as malformedOutputsRefusalMessage, vr as importableOutputs, vt as isWaitAbandonedError, w as buildFinalSnapshotIdentifier, wi as canonicalizeRegion, wn as producerRecordKey, wr as stringifyValue, wt as isTerminationProtectionPropagationError, x as bindingSkippedOutputs, xi as decodeListingKey, xn as malformedResourcePropertiesWarning, xr as orphansCarriedFrom, xt as deleteIndeterminateGuards, y as keptWholeReasonText, yi as uploadCfnTemplate, yn as malformedOutputsWarning, yr as isReadableBag, yt as slowCcOperationTimeoutMs, z as coerceWarmThroughput$1, zi as DeployCancelledError, zn as DagBuilder, zr as validateContainerRepoName, zt as resolveExplicitPhysicalId } from "./deploy-engine-efiVJ-rX.js";
1
+ import { $ as isExportAliasCollision, $i as StackHasActiveImportsError, $n as maskSecretsInText, $r as escapeRegExp, $t as withSharedDrainBudget, A as makeCanonicalizePropertiesFn, An as refuseMalformedResourcesForDestroy, Ar as rewriteTemplateAssetReferences, At as getAccountInfo, B as isWarmThroughputDecrease, Bn as TemplateParser, Br as validateContainerRepoName, Bt as assertRegionMatch, C as PRE_DELETE_SNAPSHOT_TYPES, Ci as decodeListingKey, Cn as malformedStateRefusalMessage, Cr as shouldRetainResource, Ct as disableInstanceApiTermination, D as isFinalSnapshotError, Di as derivePartitionAndUrlSuffix, Dn as refuseMalformedOutputs, Dr as buildAssetRedirectMap, Dt as carriesFetchableDynamicReference, E as createPreDeleteFinalSnapshot, Ei as canonicalizeRegion, En as refuseMalformedNestedChildOutputs, Er as WorkGraph, Et as carriesDynamicReference, F as isStatefulRecreateTargetForReplace, Fi as setAwsClients, Fn as findActionableSilentDrops, Fr as getBootstrapMarkerKey, Ft as refStateLookupFromResource, G as cyan, Gi as LocalStartServiceError, Gn as carriesSecretMask, Gr as describeDockerFailure, Gt as configStringRefusal, H as toFiniteNumber, Hi as DynamicReferenceRegionAmbiguousError, Hn as STATE_SOURCED_CROSS_GENERATION_RULES, Hr as buildDockerImage, Ht as coerceCfnInteger, I as isStatefulRecreateTargetSync, In as findSilentDropProperties, Ir as isCrossRegionRedirect, It as isSettledInstanceState, J as red, Ji as NestedStackChildDirectDestroyError, Jn as errorCauseChain, Jr as getDockerCmd, Jt as requireConfigArray, K as gray, Kn as createSecretMasker, Kr as dockerSpawnEnvWithSensitive, Kt as readConfigString, L as renderStatefulReason, Li as CdkdError, Ln as describeTypeWithThrottleRetry, Lr as parseBootstrapMarker, Lt as WAFv2WebACLProvider, M as makeSiblingClaimReader, Mi as AwsClients, Mn as repairMalformedOutputsForReadOnly, Mr as BOOTSTRAP_MARKER_PREFIX, Mt as isNamelessDynamicReferenceError, N as planOrphanAdoption, Ni as getAwsClients, Nn as repairMalformedResourcePropertiesForReadOnly, Nr as assertAssetBucketRegion, Nt as isUnboundTemplateParameter, O as refusesFinalSnapshot, Oi as AssemblyReader, On as refuseMalformedOutputsForDestroy, Or as createAssetRedirectResolver, Ot as cfnRefValueFromPhysicalId, P as MULTI_REGION_RECREATE_BLOCKED_TYPES, Pi as resetAwsClients, Pn as repairMalformedResourcesForReadOnly, Pr as ensureAssetStorage, Pt as parameterTypeMayLoseSecretIdentity, Q as exportAliasCollisionScrubWarning, Qi as ResourceUpdateNotSupportedError, Qn as maskSecretsInError, Qr as runDockerStreaming, Qt as producerRegionsFromState, R as WARM_THROUGHPUT_MEMBERS, Rn as withRetry, Rr as readBootstrapMarkerBody, Rt as normalizeAwsTagsToCfn, S as ATOMIC_FINAL_SNAPSHOT_TYPES, Si as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Sn as malformedResourcesWarning, Sr as orphansCarriedFrom, St as deleteSkipReason, T as ccRoutedFinalSnapshotError, Ti as PARTITION_TABLE, Tn as refuseDivergentRecordRegionForDestroy, Tr as stringifyValue, Tt as IntrinsicFunctionResolver, U as formatResourceLine, Ui as IntrinsicResolutionRefusalError, Un as STATE_SOURCED_READBACK_RULES, Ur as describeDockerCapturedOutput, Ut as configBooleanRefusal, V as toCfnInteger, Vi as DeployCancelledError, Vn as STATE_SOURCED_BASELINE_RULES, Vr as buildDenyExternalAccessPolicy, Vt as coerceCfnBoolean, W as bold, Wi as LocalInvokeBuildError$1, Wn as TEMPLATE_SOURCED_RULES, Wr as describeDockerExecFailure, Wt as configIntegerRefusal, X as collectDeclaredOutputNames, Xi as ProvisioningError, Xn as isSingleDynamicReferenceToken, Xr as redactDockerArgvValues, Xt as requireConfigString, Y as yellow, Yi as PartialFailureError, Yn as identityKeyFor, Yr as partitionSensitiveEnv, Yt as requireConfigObject, Z as collectPublishedOutputNames, Zi as ResourceTimeoutError, Zn as markSameGenerationBag, Zr as runDockerForeground, Zt as classifyReplaySecretRegion, _ as bagHoldsSecretExpression, _i as CFN_TEMPLATE_BODY_LIMIT, _n as malformedLocalOutputsWarning, _r as hasUnverifiableParameterRefusal, _t as CloudControlProvider, a as DeploymentEventsStore, aa as normalizeAwsError, ai as getDefaultStateBucketName, an as applyRoleArnIfSet, ar as S3StateBackend, at as collectInlinePolicyNamesManagedBySiblings, b as mergeNoChangeOutputs, bi as findLargeInlineResources, bn as malformedRenderedContainersWarning, br as isReadableBag, bt as UNSPECIFIED_SKIP_REASON, c as replayFailedOperations, ca as isRetryableTransientError, ci as resolveAutoAssetStorage, cn as callerEnvCredentials, cr as buildForceUnlockCommand, ct as wouldReturnToSdkProvider, d as updatePartialMessage, da as markNonRetryable, di as resolveStateBucketWithDefault, dn as INTRINSIC_KEYS, dr as formatLockExpiry, dt as maskerOrIdentity, ea as StackTerminationProtectionError, ei as stripControlChars, en as s3BucketArn, er as recordMaskOnlyValue, et as secretBearing, f as updatePartialReason, fa as markRedactedCause, fi as resolveStateBucketWithDefaultAndSource, fn as STATE_RESOURCES_MALFORMED, fr as shellQuote, ft as beginCommandInterruptScope, g as computeImplicitDeleteEdges, gi as ssmResolvedValueType, gn as malformedExportSourceWarning, gr as hasReadableExportSet, gt as startInterruptWatch, h as IMPLICIT_DELETE_DEPENDENCIES, ha as safeStringify, hi as warnDeprecatedNoPrefixCliFlag, hn as malformedExportNamesWarning, hr as exportNamesCarriedFrom, ht as isInterruptedWaitError, i as DeploymentEventsReader, ii as synthesisStatusMessage, in as s3BucketWebsiteUrl, ir as LockManager, it as IAMRoleProvider, j as extractDeploymentEventError, ji as resolveBucketRegion, jn as refuseMalformedState, jr as AssetModeResolver, jt as isImpossibleEmptyStoredAttribute, k as unsupportedFinalSnapshotError, ki as processStackMessages, kn as refuseMalformedResourcePropertiesForOrphan, kr as loadPublishableAssetManifest, kt as coerceParameterTypedValue, l as replayRollback, la as isThrottlingError, li as resolveCaptureObservedState, ln as envCredentialsAreAssumedRole, lr as buildLockContentionMessage, lt as createMaskedRetryLogger, m as maskingRetryLogger, ma as describeAwsFailure, mi as stateBucketExistenceConfirmed, mn as hasReadableResources, mr as DEFAULT_STATE_PREFIX, mt as interruptWatchListenerCount, n as DEFAULT_RESOURCE_WARN_AFTER_MS, na as SynthesisError, ni as getDockerImageBySourceHash, nn as s3BucketDualStackDomainName, nr as redactSecretsForState, nt as secretSafeKeyDisplay, o as planFailedOps, oa as withErrorHandling, oi as getLegacyStateBucketName, on as AWS_CREDENTIAL_ENV_KEYS, or as rebuildClientForBucketRegion, ot as clearOnUpdateRemoval, p as withResourceDeadline, pa as retryClassificationText, pi as resolveUseCdkBootstrapAssets, pn as hasReadableOutputs, pr as CUSTOM_RESOURCE_RESPONSE_PREFIX, pt as endCommandInterruptScope, q as green, qi as MalformedProducerRecordRefusalError, qn as dynamicReferenceTokens, qr as formatDockerLoginError, qt as replayWarn, r as DeployEngine, ri as Synthesizer, rn as s3BucketRegionalDomainName, rr as scrubResourceRecord, rt as getCurrentResourceSecrets, s as planRollback, sa as isMarkedNonRetryable, si as resolveApp, sn as applyCallerIdentityCredentials, sr as UNREPRODUCIBLE_LOCK_CLAUSE, st as ProviderRegistry, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as AssetManifestLoader, tn as s3BucketDomainName, tr as recoverMaskedOutput, tt as secretBearingStateKeyWarning, u as resolveReplacementOldType, ua as isTransientServerError, ui as resolveSkipPrefix, un as DiffCalculator, ur as forceQuitRecoveryClause, ut as maskDeep, v as isSecretBearingReferenceString, vi as CFN_TEMPLATE_URL_LIMIT, vn as malformedOutputsRefusalMessage, vr as importableOutputKeys, vt as isWaitAbandonedError, w as buildFinalSnapshotIdentifier, wi as expectedOwnerParam, wn as producerRecordKey, wr as AssetPublisher, wt as isTerminationProtectionPropagationError, x as bindingSkippedOutputs, xi as uploadCfnTemplate, xn as malformedResourcePropertiesWarning, xr as orphansAfterRollback, xt as deleteIndeterminateGuards, y as keptWholeReasonText, yi as MIGRATE_TMP_PREFIX, yn as malformedOutputsWarning, yr as importableOutputs, yt as slowCcOperationTimeoutMs, z as coerceWarmThroughput$1, zi as CrossAccountSecretRefusalError, zn as DagBuilder, zr as validateAssetBucketName, zt as resolveExplicitPhysicalId } from "./deploy-engine-tX_-qy5a.js";
2
2
  import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, _ as withSkipPrefix, b as SECRET_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 displayIdent, x as STACK_REF_MAX_CODE_POINTS } from "./logger-BX4pJk0T.js";
3
3
  import { n as awsClientDefaults } from "./aws-client-defaults-BoIxyCir.js";
4
- import { t as getCdkdVersion } from "./version-CpyoQ3PO.js";
5
- import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-BTxYWlwu.js";
4
+ import { t as getCdkdVersion } from "./version-BYmXwLpd.js";
5
+ import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-DWY8_Kn4.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";
@@ -12,7 +12,7 @@ import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesComman
12
12
  import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCodeSigningConfigCommand, DeleteFunctionCommand, DeleteFunctionConcurrencyCommand, DeleteFunctionEventInvokeConfigCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCodeSigningConfigCommand, GetFunctionCommand, GetFunctionConcurrencyCommand, GetFunctionEventInvokeConfigCommand, GetFunctionRecursionConfigCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand as GetPolicyCommand$1, GetRuntimeManagementConfigCommand, LambdaClient, ListTagsCommand, PublishLayerVersionCommand, PutFunctionCodeSigningConfigCommand, PutFunctionConcurrencyCommand, PutFunctionEventInvokeConfigCommand, PutFunctionRecursionConfigCommand, PutRuntimeManagementConfigCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionActiveV2, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
13
13
  import { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from "@aws-sdk/client-sts";
14
14
  import { AllocateAddressCommand, AssociateAddressCommand, AssociateIamInstanceProfileCommand, AssociateRouteTableCommand, AttachInternetGatewayCommand, AuthorizeSecurityGroupEgressCommand, AuthorizeSecurityGroupIngressCommand, CreateInternetGatewayCommand, CreateNatGatewayCommand, CreateNetworkAclCommand, CreateNetworkAclEntryCommand, CreateRouteCommand, CreateRouteTableCommand, CreateSecurityGroupCommand, CreateSubnetCommand, CreateTagsCommand, CreateVpcCommand, DeleteInternetGatewayCommand, DeleteNatGatewayCommand, DeleteNetworkAclCommand, DeleteNetworkAclEntryCommand, DeleteNetworkInterfaceCommand, DeleteRouteCommand, DeleteRouteTableCommand, DeleteSecurityGroupCommand, DeleteSubnetCommand, DeleteTagsCommand, DeleteVpcCommand, DescribeAddressesCommand, DescribeAvailabilityZonesCommand, DescribeIamInstanceProfileAssociationsCommand, DescribeInstanceAttributeCommand, DescribeInstanceCreditSpecificationsCommand, DescribeInstancesCommand, DescribeInternetGatewaysCommand, DescribeNatGatewaysCommand, DescribeNetworkAclsCommand, DescribeNetworkInterfacesCommand, DescribeRouteTablesCommand, DescribeSecurityGroupRulesCommand, DescribeSecurityGroupsCommand, DescribeSubnetsCommand, DescribeVolumesCommand, DescribeVpcAttributeCommand, DescribeVpcsCommand, DetachInternetGatewayCommand, DisassociateAddressCommand, DisassociateRouteTableCommand, EC2Client, ModifyInstanceAttributeCommand, ModifyInstanceCreditSpecificationCommand, ModifyInstanceMetadataOptionsCommand, ModifySubnetAttributeCommand, ModifyVpcAttributeCommand, MonitorInstancesCommand, ReleaseAddressCommand, ReplaceNetworkAclAssociationCommand, RevokeSecurityGroupEgressCommand, RevokeSecurityGroupIngressCommand, RunInstancesCommand, TerminateInstancesCommand, UnmonitorInstancesCommand, waitUntilInstanceRunning, waitUntilInstanceTerminated, waitUntilNatGatewayAvailable, waitUntilNatGatewayDeleted } from "@aws-sdk/client-ec2";
15
- import { CreateTableCommand, DeleteResourcePolicyCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeContributorInsightsCommand, DescribeKinesisStreamingDestinationCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DisableKinesisStreamingDestinationCommand, DynamoDBClient, EnableKinesisStreamingDestinationCommand, GetResourcePolicyCommand, ListTagsOfResourceCommand, PutResourcePolicyCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateContinuousBackupsCommand, UpdateContributorInsightsCommand, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
15
+ import { CreateTableCommand, DeleteResourcePolicyCommand, DeleteTableCommand, DescribeContinuousBackupsCommand, DescribeContributorInsightsCommand, DescribeKinesisStreamingDestinationCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DisableKinesisStreamingDestinationCommand, DynamoDBClient, EnableKinesisStreamingDestinationCommand, GetResourcePolicyCommand, ListTagsOfResourceCommand, PolicyNotFoundException, PutResourcePolicyCommand, ResourceNotFoundException as ResourceNotFoundException$1, TagResourceCommand as TagResourceCommand$2, UntagResourceCommand as UntagResourceCommand$2, UpdateContinuousBackupsCommand, UpdateContributorInsightsCommand, UpdateTableCommand, UpdateTimeToLiveCommand } from "@aws-sdk/client-dynamodb";
16
16
  import { CloudFormationClient, CreateChangeSetCommand, DeleteChangeSetCommand, DeleteStackCommand, DescribeChangeSetCommand, DescribeStackEventsCommand, DescribeStackResourcesCommand, DescribeStacksCommand, ExecuteChangeSetCommand, GetTemplateCommand, UpdateStackCommand, waitUntilChangeSetCreateComplete, waitUntilStackDeleteComplete, waitUntilStackImportComplete, waitUntilStackUpdateComplete } from "@aws-sdk/client-cloudformation";
17
17
  import { APIGatewayClient, CreateAuthorizerCommand, CreateDeploymentCommand, CreateResourceCommand, CreateStageCommand, DeleteAuthorizerCommand, DeleteDeploymentCommand, DeleteMethodCommand, DeleteResourceCommand, DeleteStageCommand, GetAccountCommand, GetAuthorizerCommand, GetDeploymentCommand, GetMethodCommand, GetResourceCommand, GetStageCommand, NotFoundException as NotFoundException$1, PutIntegrationCommand, PutIntegrationResponseCommand, PutMethodCommand, PutMethodResponseCommand, TagResourceCommand as TagResourceCommand$3, UntagResourceCommand as UntagResourceCommand$3, UpdateAccountCommand, UpdateAuthorizerCommand, UpdateMethodCommand, UpdateStageCommand } from "@aws-sdk/client-api-gateway";
18
18
  import { CreateEventBusCommand, DeleteEventBusCommand, DeleteRuleCommand, DescribeEventBusCommand, DescribeRuleCommand, EventBridgeClient, ListRulesCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$1, ListTargetsByRuleCommand, PutRuleCommand, PutTargetsCommand, RemoveTargetsCommand, ResourceNotFoundException as ResourceNotFoundException$2, TagResourceCommand as TagResourceCommand$4, UntagResourceCommand as UntagResourceCommand$4, UpdateEventBusCommand } from "@aws-sdk/client-eventbridge";
@@ -7137,7 +7137,7 @@ function manualBucketDeletionClause(bucketName, maskSecrets) {
7137
7137
  * disagree about what an `Id` is.
7138
7138
  */
7139
7139
  function configItemId(item) {
7140
- if (!isPlainObject$1(item)) return void 0;
7140
+ if (!isPlainObject$2(item)) return void 0;
7141
7141
  const id = item["Id"];
7142
7142
  if (typeof id === "string") return id;
7143
7143
  if (typeof id === "number" && Number.isFinite(id)) return String(id);
@@ -7157,7 +7157,7 @@ function configItemId(item) {
7157
7157
  function declaresBucketObjectLock(properties) {
7158
7158
  return properties["ObjectLockEnabled"] === true || properties["ObjectLockEnabled"] === "true";
7159
7159
  }
7160
- function isPlainObject$1(value) {
7160
+ function isPlainObject$2(value) {
7161
7161
  return typeof value === "object" && value !== null && !Array.isArray(value);
7162
7162
  }
7163
7163
  /**
@@ -7192,7 +7192,7 @@ function isFlattenedDestination(dest) {
7192
7192
  * via the critic's `--providers-dir=` seam.
7193
7193
  */
7194
7194
  function s3BucketDestinationPath(dest, destinationPath) {
7195
- return isPlainObject$1(dest) && isFlattenedDestination(dest) ? destinationPath : `${destinationPath}.S3BucketDestination`;
7195
+ return isPlainObject$2(dest) && isFlattenedDestination(dest) ? destinationPath : `${destinationPath}.S3BucketDestination`;
7196
7196
  }
7197
7197
  /**
7198
7198
  * The FLATTENED CFn `Destination` block cdkd actually SENDS for `declared`
@@ -7231,9 +7231,9 @@ function s3BucketDestinationPath(dest, destinationPath) {
7231
7231
  * resolver's own refusal to report).
7232
7232
  */
7233
7233
  function effectiveS3BucketDestination(declared, sentFormat) {
7234
- if (!isPlainObject$1(declared)) return declared;
7234
+ if (!isPlainObject$2(declared)) return declared;
7235
7235
  const bag = isFlattenedDestination(declared) ? declared : declared["S3BucketDestination"];
7236
- if (!isPlainObject$1(bag)) return declared;
7236
+ if (!isPlainObject$2(bag)) return declared;
7237
7237
  if (!bag["BucketArn"] && !bag["Bucket"]) return declared;
7238
7238
  const out = {};
7239
7239
  const bucketArn = bag["BucketArn"] ?? bag["Bucket"];
@@ -7364,7 +7364,7 @@ function effectiveInventoryItem(item, sent) {
7364
7364
  const scheduleContainerUsable = configStringRefusal(declaredSchedule, "Frequency", INVENTORY_DEFAULT_SCHEDULE_FREQUENCY, "AWS::S3::Bucket InventoryConfigurations[].Schedule") === void 0;
7365
7365
  const canFoldSchedule = sent?.frequency !== void 0 || scheduleFrequencyUsable || !declaresScheduleFrequency && scheduleContainerUsable;
7366
7366
  if (canFoldSchedule) {
7367
- const declaredFrequency = declaresScheduleFrequency ? item["ScheduleFrequency"] : isPlainObject$1(declaredSchedule) && declaredSchedule["Frequency"] !== void 0 ? declaredSchedule["Frequency"] : INVENTORY_DEFAULT_SCHEDULE_FREQUENCY;
7367
+ const declaredFrequency = declaresScheduleFrequency ? item["ScheduleFrequency"] : isPlainObject$2(declaredSchedule) && declaredSchedule["Frequency"] !== void 0 ? declaredSchedule["Frequency"] : INVENTORY_DEFAULT_SCHEDULE_FREQUENCY;
7368
7368
  const frequency = sent?.frequency ?? declaredFrequency;
7369
7369
  if (!Object.is(item["ScheduleFrequency"], frequency)) patch["ScheduleFrequency"] = frequency;
7370
7370
  }
@@ -7395,9 +7395,9 @@ function effectiveInventoryItem(item, sent) {
7395
7395
  */
7396
7396
  function effectiveAnalyticsItem(item, sent) {
7397
7397
  const storageClassAnalysis = item["StorageClassAnalysis"];
7398
- if (!isPlainObject$1(storageClassAnalysis)) return item;
7398
+ if (!isPlainObject$2(storageClassAnalysis)) return item;
7399
7399
  const dataExport = storageClassAnalysis["DataExport"];
7400
- if (!isPlainObject$1(dataExport)) return item;
7400
+ if (!isPlainObject$2(dataExport)) return item;
7401
7401
  const patch = {};
7402
7402
  const outputSchemaVersion = sent?.outputSchemaVersion ?? declaredOrDefault(dataExport, "OutputSchemaVersion", ANALYTICS_DEFAULT_OUTPUT_SCHEMA_VERSION);
7403
7403
  if (!Object.is(dataExport["OutputSchemaVersion"], outputSchemaVersion)) patch["OutputSchemaVersion"] = outputSchemaVersion;
@@ -7513,7 +7513,7 @@ const EVENTBRIDGE_DEFAULT_ENABLED = true;
7513
7513
  * correct once every declared-but-unusable value has been refused.
7514
7514
  */
7515
7515
  function eventBridgeEnabledOf(eb) {
7516
- const declared = isPlainObject$1(eb) ? eb["EventBridgeEnabled"] : void 0;
7516
+ const declared = isPlainObject$2(eb) ? eb["EventBridgeEnabled"] : void 0;
7517
7517
  return coerceCfnBoolean(declared) ?? EVENTBRIDGE_DEFAULT_ENABLED;
7518
7518
  }
7519
7519
  /** The notification families whose empty declaration is never SENT (issue #1759). */
@@ -7620,7 +7620,7 @@ function foldNotificationEvents(item) {
7620
7620
  * handling).
7621
7621
  */
7622
7622
  function effectiveNotificationConfiguration(config) {
7623
- if (!isPlainObject$1(config)) return config;
7623
+ if (!isPlainObject$2(config)) return config;
7624
7624
  let out = config;
7625
7625
  for (const [listKey, aliases] of NOTIFICATION_ARN_ALIASES) out = canonicalizeItemList(out, listKey, (item) => foldNotificationEvents(foldToleratedAliases(item, aliases)));
7626
7626
  out = dropEmptyNotificationFamilies(out);
@@ -7677,8 +7677,8 @@ function foldEventBridgeConfiguration(config) {
7677
7677
  if (eb === void 0) return config;
7678
7678
  if (eb !== null && configBooleanRefusal(eb, "EventBridgeEnabled", EVENTBRIDGE_CONFIG_PATH)) return config;
7679
7679
  const enabled = eb === null ? false : eventBridgeEnabledOf(eb);
7680
- const declared = isPlainObject$1(eb) ? eb["EventBridgeEnabled"] : void 0;
7681
- if (isPlainObject$1(eb) && Object.keys(eb).length === 1 && Object.is(declared, enabled)) return config;
7680
+ const declared = isPlainObject$2(eb) ? eb["EventBridgeEnabled"] : void 0;
7681
+ if (isPlainObject$2(eb) && Object.keys(eb).length === 1 && Object.is(declared, enabled)) return config;
7682
7682
  return {
7683
7683
  ...config,
7684
7684
  [EVENTBRIDGE_CONFIG_KEY]: { EventBridgeEnabled: enabled }
@@ -7758,7 +7758,7 @@ function deepSameValue(a, b) {
7758
7758
  if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
7759
7759
  return a.every((element, index) => deepSameValue(element, b[index]));
7760
7760
  }
7761
- if (isPlainObject$1(a) && isPlainObject$1(b)) {
7761
+ if (isPlainObject$2(a) && isPlainObject$2(b)) {
7762
7762
  const keysA = Object.keys(a);
7763
7763
  if (keysA.length !== Object.keys(b).length) return false;
7764
7764
  return keysA.every((key) => key in b && deepSameValue(a[key], b[key]));
@@ -7833,9 +7833,9 @@ function foldLegacySingularTransitions(rule) {
7833
7833
  let out;
7834
7834
  for (const [pluralKey, singularKey] of LEGACY_SINGULAR_TRANSITIONS) {
7835
7835
  const singular = rule[singularKey];
7836
- const hasSingular = isPlainObject$1(singular);
7836
+ const hasSingular = isPlainObject$2(singular);
7837
7837
  const plural = rule[pluralKey];
7838
- const needsFilter = Array.isArray(plural) && plural.some((element) => !isPlainObject$1(element));
7838
+ const needsFilter = Array.isArray(plural) && plural.some((element) => !isPlainObject$2(element));
7839
7839
  if (!hasSingular && !needsFilter) continue;
7840
7840
  let collided = false;
7841
7841
  const merged = mergeLegacySingular(plural, hasSingular ? singular : void 0, () => {
@@ -7943,7 +7943,7 @@ function foldLifecycleExpiration(rule) {
7943
7943
  const iso = cfnDateToIso(expiration);
7944
7944
  if (iso === void 0) return rule;
7945
7945
  date = iso;
7946
- } else if (isPlainObject$1(expiration)) {
7946
+ } else if (isPlainObject$2(expiration)) {
7947
7947
  if (expiration["Days"] !== void 0) days = expiration["Days"];
7948
7948
  if (expiration["Date"]) {
7949
7949
  const iso = cfnDateToIso(expiration["Date"]);
@@ -7991,10 +7991,10 @@ function foldNoncurrentVersionExpiration(rule) {
7991
7991
  const legacy = rule["NoncurrentVersionExpirationInDays"];
7992
7992
  if (legacy === void 0) return rule;
7993
7993
  const declaredObject = rule["NoncurrentVersionExpiration"];
7994
- if (declaredObject !== void 0 && !isPlainObject$1(declaredObject)) return rule;
7994
+ if (declaredObject !== void 0 && !isPlainObject$2(declaredObject)) return rule;
7995
7995
  const out = { ...rule };
7996
7996
  delete out["NoncurrentVersionExpirationInDays"];
7997
- if (isPlainObject$1(declaredObject)) return out;
7997
+ if (isPlainObject$2(declaredObject)) return out;
7998
7998
  const days = coerceCfnNumber(legacy);
7999
7999
  if (days === void 0) return rule;
8000
8000
  out["NoncurrentVersionExpiration"] = { NoncurrentDays: days };
@@ -8089,10 +8089,10 @@ function foldLifecycleScope(rule, useFilterForm) {
8089
8089
  * @returns `config` BY IDENTITY when the declared bag IS what goes on the wire.
8090
8090
  */
8091
8091
  function effectiveLifecycleConfiguration(config) {
8092
- if (!isPlainObject$1(config)) return config;
8092
+ if (!isPlainObject$2(config)) return config;
8093
8093
  const rules = config["Rules"];
8094
8094
  if (!Array.isArray(rules)) return config;
8095
- const objectRules = rules.filter(isPlainObject$1);
8095
+ const objectRules = rules.filter(isPlainObject$2);
8096
8096
  if (objectRules.length !== rules.length || objectRules.length === 0) return config;
8097
8097
  if (objectRules.some(lifecycleRuleRefused)) return config;
8098
8098
  const useFilterForm = lifecycleUsesFilterForm(objectRules);
@@ -8178,7 +8178,7 @@ function canonicalizeItemList(properties, key, fold) {
8178
8178
  if (!Array.isArray(list)) return properties;
8179
8179
  let changed = false;
8180
8180
  const mapped = list.map((element) => {
8181
- if (!isPlainObject$1(element)) return element;
8181
+ if (!isPlainObject$2(element)) return element;
8182
8182
  const next = fold(element);
8183
8183
  if (next !== element) changed = true;
8184
8184
  return next;
@@ -8198,7 +8198,7 @@ function describeValue$1(value, maskSecrets = (text) => text) {
8198
8198
  if (value === null) return "null";
8199
8199
  if (Array.isArray(value)) return "an array";
8200
8200
  if (typeof value === "string") return `a string ${JSON.stringify(maskDeep(value, maskSecrets))}`;
8201
- if (isPlainObject$1(value)) return `an object with keys [${Object.keys(value).map((key) => maskSecrets(key)).join(", ")}]`;
8201
+ if (isPlainObject$2(value)) return `an object with keys [${Object.keys(value).map((key) => maskSecrets(key)).join(", ")}]`;
8202
8202
  return `a ${typeof value} (${maskSecrets(String(value))})`;
8203
8203
  }
8204
8204
  /**
@@ -8223,8 +8223,8 @@ function coerceCfnNumber(value) {
8223
8223
  * StorageClass collision, and say so rather than dropping in silence.
8224
8224
  */
8225
8225
  function mergeLegacySingular(plural, singular, onCollision) {
8226
- const out = (Array.isArray(plural) ? plural : []).filter(isPlainObject$1);
8227
- if (!isPlainObject$1(singular)) return out;
8226
+ const out = (Array.isArray(plural) ? plural : []).filter(isPlainObject$2);
8227
+ if (!isPlainObject$2(singular)) return out;
8228
8228
  const sc = singular["StorageClass"];
8229
8229
  if (typeof sc === "string" && out.some((e) => e["StorageClass"] === sc)) {
8230
8230
  onCollision(sc);
@@ -8722,7 +8722,7 @@ var S3BucketProvider = class S3BucketProvider {
8722
8722
  if (!(exp?.["Days"] !== void 0 || exp?.["Date"] !== void 0)) sdkRule.Expiration = { ExpiredObjectDeleteMarker: true };
8723
8723
  else this.logger.warn(`Lifecycle rule '${rule["Id"] ?? "<unnamed>"}' on ${bucketName} sets ExpiredObjectDeleteMarker alongside an expiration Days/Date; S3 forbids combining them, so the delete-marker cleanup was not applied.`);
8724
8724
  }
8725
- const nve = isPlainObject$1(rule["NoncurrentVersionExpiration"]) ? rule["NoncurrentVersionExpiration"] : void 0;
8725
+ const nve = isPlainObject$2(rule["NoncurrentVersionExpiration"]) ? rule["NoncurrentVersionExpiration"] : void 0;
8726
8726
  const legacyNveDays = rule["NoncurrentVersionExpirationInDays"];
8727
8727
  if (nve) sdkRule.NoncurrentVersionExpiration = {
8728
8728
  NoncurrentDays: nve["NoncurrentDays"],
@@ -8747,7 +8747,7 @@ var S3BucketProvider = class S3BucketProvider {
8747
8747
  const singularTransition = rule["Transition"];
8748
8748
  const allTransitions = mergeLegacySingular(transitions, singularTransition, (sc) => this.logger.warn(`Lifecycle rule '${rule["Id"] ?? "<unnamed>"}' on ${bucketName} declares both Transitions and the legacy Transition for storage class ${sc}; S3 rejects duplicates, so the legacy singular was ignored.`));
8749
8749
  if (allTransitions.length > 0) sdkRule.Transitions = allTransitions.map(toSdkTransition);
8750
- const abort = isPlainObject$1(rule["AbortIncompleteMultipartUpload"]) ? rule["AbortIncompleteMultipartUpload"] : void 0;
8750
+ const abort = isPlainObject$2(rule["AbortIncompleteMultipartUpload"]) ? rule["AbortIncompleteMultipartUpload"] : void 0;
8751
8751
  if (abort) sdkRule.AbortIncompleteMultipartUpload = { DaysAfterInitiation: abort["DaysAfterInitiation"] };
8752
8752
  const { prefix, tagFilters, sizeGt, sizeLt } = lifecycleRuleScope(rule);
8753
8753
  const hasTags = tagFilters != null && tagFilters.length > 0;
@@ -9081,10 +9081,10 @@ var S3BucketProvider = class S3BucketProvider {
9081
9081
  throw new Error(message);
9082
9082
  };
9083
9083
  if (dest === void 0 || dest === null) return;
9084
- if (!isPlainObject$1(dest)) return drop(`${destinationPath} must be an object (got ${describeValue$1(dest, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
9084
+ if (!isPlainObject$2(dest)) return drop(`${destinationPath} must be an object (got ${describeValue$1(dest, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
9085
9085
  const bag = isFlattenedDestination(dest) ? dest : dest["S3BucketDestination"];
9086
9086
  if (bag === void 0 || bag === null) return drop(`${destinationPath} carries neither a bucket (BucketArn / Bucket) nor a nested S3BucketDestination object (got ${describeValue$1(dest, maskSecrets)}) — the destination would otherwise be dropped from the request with no error`);
9087
- if (!isPlainObject$1(bag)) return drop(`${nestedPath} must be an object (got ${describeValue$1(bag, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
9087
+ if (!isPlainObject$2(bag)) return drop(`${nestedPath} must be an object (got ${describeValue$1(bag, maskSecrets)}) — check for an unresolved intrinsic or a mis-nested template value; the destination would otherwise be dropped from the request with no error`);
9088
9088
  if (!bag["BucketArn"] && !bag["Bucket"]) return drop(`${bag === dest ? destinationPath : nestedPath} has no destination bucket (BucketArn / Bucket) (got ${describeValue$1(bag, maskSecrets)}) — the request would be rejected by S3, or silently carry no destination`);
9089
9089
  return bag;
9090
9090
  }
@@ -16943,6 +16943,511 @@ function isRecord$1(value) {
16943
16943
  return typeof value === "object" && value !== null && !Array.isArray(value);
16944
16944
  }
16945
16945
 
16946
+ //#endregion
16947
+ //#region src/provisioning/dynamodb-contributor-insights.ts
16948
+ /**
16949
+ * The `AWS::DynamoDB::Table` `ContributorInsightsSpecification` rules, in ONE
16950
+ * spelling for the table-level block and the per-index
16951
+ * (`GlobalSecondaryIndexes[].ContributorInsightsSpecification`) one (issue
16952
+ * [#1782](https://github.com/go-to-k/cdkd/issues/1782)).
16953
+ *
16954
+ * Neither block rides `CreateTable` / `UpdateTable`: DynamoDB applies it
16955
+ * through `UpdateContributorInsights` and reports it through
16956
+ * `DescribeContributorInsights`, and both calls address an index by adding
16957
+ * `IndexName`. The SDK's `GlobalSecondaryIndex` shape has NO such member, so a
16958
+ * per-index block left inside the forwarded index entry is discarded by the
16959
+ * serializer — a template asking for per-index Contributor Insights deployed
16960
+ * green with the feature off.
16961
+ *
16962
+ * Pure, so the write plan and the drift read-back gate cannot answer "does
16963
+ * this index declare the block" differently.
16964
+ */
16965
+ /** The CFn property name, shared by the table-level and the per-index block. */
16966
+ const CONTRIBUTOR_INSIGHTS_KEY = "ContributorInsightsSpecification";
16967
+ /**
16968
+ * Read a `ContributorInsightsSpecification` block.
16969
+ *
16970
+ * An ABSENT `Enabled` reads as `false` — the member is required by the CFn
16971
+ * schema, and disabling is the answer that cannot start a billed feature.
16972
+ */
16973
+ function readContributorInsightsSpec(value, path) {
16974
+ if (value === void 0 || value === null) return { kind: "absent" };
16975
+ const refusal = configBooleanRefusal(value, "Enabled", path) ?? configStringRefusal(value, "Mode", "", path);
16976
+ if (refusal !== void 0) return {
16977
+ kind: "unusable",
16978
+ reason: refusal
16979
+ };
16980
+ const block = value;
16981
+ const enabled = coerceCfnBoolean(block["Enabled"]) ?? false;
16982
+ const mode = block["Mode"];
16983
+ return typeof mode === "string" && mode.length > 0 ? {
16984
+ kind: "usable",
16985
+ enabled,
16986
+ mode
16987
+ } : {
16988
+ kind: "usable",
16989
+ enabled
16990
+ };
16991
+ }
16992
+ /**
16993
+ * The call one block needs, given the block the PREVIOUS side recorded.
16994
+ *
16995
+ * - `absent` with a DECLARED previous (usable or not) is a template REMOVAL and
16996
+ * DISABLES — the rule this provider's other post-ACTIVE blocks (PITR, TTL,
16997
+ * Kinesis) and the table-level block already follow. Because the rule is
16998
+ * symmetric in its two sides, a rollback — which replays `update()` with the
16999
+ * sides SWAPPED — restores the old setting through the same arms.
17000
+ * - `unusable` issues NOTHING and reports through `onUnusable`: the live
17001
+ * setting is left alone rather than overwritten with a guess.
17002
+ * - An unchanged usable block issues nothing. `mode` takes part only while
17003
+ * enabled, since it is never sent with `DISABLE`.
17004
+ */
17005
+ function planContributorInsightsOp(desired, previous, onUnusable) {
17006
+ if (desired.kind === "unusable") {
17007
+ onUnusable(desired.reason);
17008
+ return;
17009
+ }
17010
+ if (desired.kind === "absent") return previous.kind === "absent" ? void 0 : { action: "DISABLE" };
17011
+ if (previous.kind === "usable" && previous.enabled === desired.enabled && (!desired.enabled || previous.mode === desired.mode)) return;
17012
+ if (!desired.enabled) return { action: "DISABLE" };
17013
+ return desired.mode === void 0 ? { action: "ENABLE" } : {
17014
+ action: "ENABLE",
17015
+ mode: desired.mode
17016
+ };
17017
+ }
17018
+ /**
17019
+ * The path a per-index refusal names. Deliberately WITHOUT the index name: the
17020
+ * name is a RESOLVED template value, the caller's message already carries it
17021
+ * through a whole-value mask, and a copy embedded here would reach only the
17022
+ * substring mask, which ignores a name shorter than its minimum needle.
17023
+ */
17024
+ const INDEX_CONTRIBUTOR_INSIGHTS_PATH = `GlobalSecondaryIndexes[].${CONTRIBUTOR_INSIGHTS_KEY}`;
17025
+ /** Index a CFn `GlobalSecondaryIndexes` value by `IndexName`. */
17026
+ function indexEntriesByName(value) {
17027
+ const byName = /* @__PURE__ */ new Map();
17028
+ if (!Array.isArray(value)) return byName;
17029
+ for (const entry of value) {
17030
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
17031
+ const name = entry["IndexName"];
17032
+ if (typeof name === "string") byName.set(name, entry);
17033
+ }
17034
+ return byName;
17035
+ }
17036
+ /**
17037
+ * The per-index calls an index list needs, in DESIRED order.
17038
+ *
17039
+ * Only an index the DESIRED list still names can produce a call: an index the
17040
+ * update removed takes its Contributor Insights rules with it, and a call
17041
+ * naming it fails with `ResourceNotFoundException`. An index that is NEW in
17042
+ * the desired list has an `absent` previous side, so its declared block is
17043
+ * always applied.
17044
+ */
17045
+ function planIndexContributorInsightsOps(desiredIndexes, previousIndexes, onUnusable) {
17046
+ const previousByName = indexEntriesByName(previousIndexes);
17047
+ const ops = [];
17048
+ for (const [indexName, entry] of indexEntriesByName(desiredIndexes)) {
17049
+ const path = INDEX_CONTRIBUTOR_INSIGHTS_PATH;
17050
+ const op = planContributorInsightsOp(readContributorInsightsSpec(entry[CONTRIBUTOR_INSIGHTS_KEY], path), readContributorInsightsSpec(previousByName.get(indexName)?.[CONTRIBUTOR_INSIGHTS_KEY], path), (reason) => onUnusable(indexName, reason));
17051
+ if (op !== void 0) ops.push({
17052
+ indexName,
17053
+ ...op
17054
+ });
17055
+ }
17056
+ return ops;
17057
+ }
17058
+ /**
17059
+ * Every refusal a CREATE must raise before `CreateTable` goes out: the
17060
+ * table-level block's and each index's. A create has no live setting to leave
17061
+ * alone, so an unreadable block there is a template error, not a skip.
17062
+ */
17063
+ function contributorInsightsRefusals(tableBlock, indexes) {
17064
+ const refusals = [];
17065
+ const table = readContributorInsightsSpec(tableBlock, CONTRIBUTOR_INSIGHTS_KEY);
17066
+ if (table.kind === "unusable") refusals.push(table.reason);
17067
+ if (!Array.isArray(indexes)) return refusals;
17068
+ indexes.forEach((entry, position) => {
17069
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return;
17070
+ const read = readContributorInsightsSpec(entry[CONTRIBUTOR_INSIGHTS_KEY], `GlobalSecondaryIndexes[${position}].${CONTRIBUTOR_INSIGHTS_KEY}`);
17071
+ if (read.kind === "unusable") refusals.push(read.reason);
17072
+ });
17073
+ return refusals;
17074
+ }
17075
+ /**
17076
+ * One index entry WITHOUT its `ContributorInsightsSpecification`, for the
17077
+ * `CreateTable` forward.
17078
+ *
17079
+ * Explicit so nothing relies on the SDK serializer discarding a member its
17080
+ * `GlobalSecondaryIndex` shape does not have. Returns the entry UNCHANGED —
17081
+ * identity included — when there is nothing to strip, and never mutates it: the
17082
+ * bag is the caller's resolved template, which the engine records into state.
17083
+ */
17084
+ function stripIndexContributorInsights(entry) {
17085
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
17086
+ if (!("ContributorInsightsSpecification" in entry)) return entry;
17087
+ const { [CONTRIBUTOR_INSIGHTS_KEY]: _stripped, ...rest } = entry;
17088
+ return rest;
17089
+ }
17090
+ /**
17091
+ * Does this DESIRED index entry declare a block cdkd would act on — the gate
17092
+ * for the per-index drift read-back, which costs one API call per index?
17093
+ *
17094
+ * The same reader the write plan runs, so an `unusable` block (never sent) is
17095
+ * never read back either.
17096
+ */
17097
+ function indexDeclaresContributorInsights(desiredEntry) {
17098
+ return readContributorInsightsSpec(desiredEntry?.[CONTRIBUTOR_INSIGHTS_KEY], "").kind === "usable";
17099
+ }
17100
+ /** The setting a `ContributorInsightsStatus` stands for, if any. */
17101
+ function enabledForStatus(status, transientAsTarget) {
17102
+ if (status === "ENABLED" || transientAsTarget && status === "ENABLING") return true;
17103
+ if (status === "DISABLED" || transientAsTarget && status === "DISABLING") return false;
17104
+ }
17105
+ /**
17106
+ * Reverse-map the TABLE-level `DescribeContributorInsights` answer to the CFn
17107
+ * block, or `undefined` when there is nothing stable to report.
17108
+ *
17109
+ * Only the terminal statuses are mapped, and `Mode` is emitted whenever AWS
17110
+ * reports one for an enabled table — the block is a TOP-LEVEL key, which the
17111
+ * comparator walks by the baseline's own keys. `Mode` is never emitted for a
17112
+ * disabled block: it is not sent with `DISABLE`, and a disabled block carrying
17113
+ * one is a CFn-invalid placeholder.
17114
+ */
17115
+ function reverseMapTableContributorInsights(status, mode) {
17116
+ const enabled = enabledForStatus(status, false);
17117
+ if (enabled === void 0) return void 0;
17118
+ const block = { Enabled: enabled };
17119
+ if (enabled && mode !== void 0) block["Mode"] = mode;
17120
+ return block;
17121
+ }
17122
+ /**
17123
+ * Reverse-map one PER-INDEX `DescribeContributorInsights` answer, shaped by
17124
+ * the block the DESIRED entry declares. `undefined` when there is nothing to
17125
+ * report or the declared block is not one cdkd acts on.
17126
+ *
17127
+ * The per-index block sits inside an ARRAY entry, which the comparator matches
17128
+ * as a whole and by exact key set, so everything here exists to make the
17129
+ * read-back equal the declaration whenever AWS holds what was declared:
17130
+ *
17131
+ * - `ENABLING` / `DISABLING` read as their TARGET. The deploy does not wait
17132
+ * for the toggle, so the post-deploy `observedProperties` capture usually
17133
+ * sees `ENABLING`; omitting the block would freeze an entry the settled
17134
+ * index can never equal.
17135
+ * - `Mode` is emitted only when the declared block carries one: AWS reports a
17136
+ * mode for every enabled rule set (its default when none was sent). While
17137
+ * `ENABLING`, a mode AWS does not report yet reads as the declared one —
17138
+ * the one that was just sent.
17139
+ * - `Enabled` keeps the declared SPELLING when the template wrote a string
17140
+ * (`'true'`), since the comparator does not coerce.
17141
+ */
17142
+ function reverseMapIndexContributorInsights(status, mode, declaredBlock) {
17143
+ const declared = readContributorInsightsSpec(declaredBlock, INDEX_CONTRIBUTOR_INSIGHTS_PATH);
17144
+ if (declared.kind !== "usable") return void 0;
17145
+ const enabled = enabledForStatus(status, true);
17146
+ if (enabled === void 0) return void 0;
17147
+ const declaredEnabled = declaredBlock["Enabled"];
17148
+ const block = { Enabled: typeof declaredEnabled !== "string" ? enabled : declared.enabled === enabled ? declaredEnabled : String(enabled) };
17149
+ if (enabled && declared.mode !== void 0) {
17150
+ const liveMode = mode ?? (status === "ENABLING" ? declared.mode : void 0);
17151
+ if (liveMode !== void 0) block["Mode"] = liveMode;
17152
+ }
17153
+ return block;
17154
+ }
17155
+
17156
+ //#endregion
17157
+ //#region src/provisioning/dynamodb-stream-members.ts
17158
+ /**
17159
+ * The two `AWS::DynamoDB::Table` `StreamSpecification` members that are NOT
17160
+ * members of the SDK's `StreamSpecification`: `ResourcePolicy`
17161
+ * (`{ PolicyDocument }`, the resource-based policy of the STREAM) and `Tags`
17162
+ * (issue [#3458](https://github.com/go-to-k/cdkd/issues/3458)).
17163
+ *
17164
+ * `CreateTable` / `UpdateTable` take only `{ StreamEnabled, StreamViewType }`,
17165
+ * so a block carrying either member deployed green with neither applied — a
17166
+ * template granting access to a table's stream left the stream with no policy.
17167
+ * CloudFormation applies both against the STREAM arn (`LatestStreamArn`),
17168
+ * re-applies both after a `StreamViewType` change (which mints a NEW arn), and
17169
+ * REMOVES both when the member leaves a block whose stream stays (measured on
17170
+ * real CloudFormation; the measurements are on the issue).
17171
+ *
17172
+ * Pure, so the write plan, the create-time refusal and the drift read-back
17173
+ * gate cannot answer "does this block declare the member" differently.
17174
+ */
17175
+ /** The CFn property holding both members. */
17176
+ const STREAM_SPECIFICATION_KEY = "StreamSpecification";
17177
+ /** The stream's resource-based policy member. */
17178
+ const STREAM_POLICY_KEY = "ResourcePolicy";
17179
+ /** The stream's tag-list member. */
17180
+ const STREAM_TAGS_KEY = "Tags";
17181
+ const POLICY_PATH = `${STREAM_SPECIFICATION_KEY}.${STREAM_POLICY_KEY}`;
17182
+ const TAGS_PATH = `${STREAM_SPECIFICATION_KEY}.${STREAM_TAGS_KEY}`;
17183
+ function isPlainObject$1(value) {
17184
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17185
+ }
17186
+ /** The message a `config-shape` guard would throw, or `undefined`. */
17187
+ function refusalOf(guard) {
17188
+ try {
17189
+ guard();
17190
+ return;
17191
+ } catch (error) {
17192
+ return describeAwsFailure(error).detail;
17193
+ }
17194
+ }
17195
+ /** The members of a `StreamSpecification` block, or none for a non-object. */
17196
+ function blockMembers(block) {
17197
+ return isPlainObject$1(block) ? block : {};
17198
+ }
17199
+ /**
17200
+ * Read `StreamSpecification.ResourcePolicy`.
17201
+ *
17202
+ * The CFn shape is `{ PolicyDocument: <object | JSON string> }` and the
17203
+ * document is REQUIRED, so a block without one is unreadable rather than "no
17204
+ * policy": reading it as absent would DELETE the live policy on an update.
17205
+ */
17206
+ function readStreamPolicy(block) {
17207
+ const value = blockMembers(block)[STREAM_POLICY_KEY];
17208
+ if (value === void 0 || value === null) return { kind: "absent" };
17209
+ const containerRefusal = refusalOf(() => requireConfigObject(value, POLICY_PATH));
17210
+ if (containerRefusal !== void 0) return {
17211
+ kind: "unusable",
17212
+ reason: containerRefusal
17213
+ };
17214
+ const documentPath = `${POLICY_PATH}.PolicyDocument`;
17215
+ const document = value["PolicyDocument"];
17216
+ if (typeof document === "string") return document.trim() === "" ? {
17217
+ kind: "unusable",
17218
+ reason: `${documentPath} must not be a blank string`
17219
+ } : {
17220
+ kind: "usable",
17221
+ document
17222
+ };
17223
+ if (document === void 0 || document === null) return {
17224
+ kind: "unusable",
17225
+ reason: `${documentPath} is required when ${POLICY_PATH} is declared`
17226
+ };
17227
+ const documentRefusal = refusalOf(() => requireConfigObject(document, documentPath));
17228
+ if (documentRefusal !== void 0) return {
17229
+ kind: "unusable",
17230
+ reason: documentRefusal
17231
+ };
17232
+ return {
17233
+ kind: "usable",
17234
+ document: JSON.stringify(document)
17235
+ };
17236
+ }
17237
+ /**
17238
+ * Read `StreamSpecification.Tags`.
17239
+ *
17240
+ * Refusals name an entry by POSITION, never by its key: the key is a RESOLVED
17241
+ * template value, and a copy embedded here would reach only the substring
17242
+ * mask, which ignores a value shorter than its minimum needle.
17243
+ *
17244
+ * ONE unreadable entry makes the whole list unusable. Filtering it out would
17245
+ * send a list nobody declared, and on an update would untag a key the
17246
+ * unreadable entry may well have named.
17247
+ */
17248
+ function readStreamTags(block) {
17249
+ const value = blockMembers(block)[STREAM_TAGS_KEY];
17250
+ if (value === void 0 || value === null) return { kind: "absent" };
17251
+ if (!Array.isArray(value)) return {
17252
+ kind: "unusable",
17253
+ reason: `${TAGS_PATH} must be an array of {Key, Value} entries`
17254
+ };
17255
+ const tags = /* @__PURE__ */ new Map();
17256
+ for (const [position, entry] of value.entries()) {
17257
+ const entryPath = `${TAGS_PATH}[${position}]`;
17258
+ const refusal = refusalOf(() => requireConfigObject(entry, entryPath)) ?? configStringRefusal(entry, "Key", "required", entryPath) ?? configStringRefusal(entry, "Value", "", entryPath);
17259
+ if (refusal !== void 0) return {
17260
+ kind: "unusable",
17261
+ reason: refusal
17262
+ };
17263
+ const { Key: key, Value: tagValue } = entry;
17264
+ if (typeof key !== "string") return {
17265
+ kind: "unusable",
17266
+ reason: `${entryPath}.Key is required`
17267
+ };
17268
+ tags.set(key, typeof tagValue === "string" ? tagValue : "");
17269
+ }
17270
+ return {
17271
+ kind: "usable",
17272
+ tags
17273
+ };
17274
+ }
17275
+ /**
17276
+ * Every refusal a CREATE must raise before `CreateTable` goes out. A create has
17277
+ * no live setting to leave alone, so an unreadable member there is a template
17278
+ * error, not a skip — and refusing after the table exists would create it only
17279
+ * to delete it again.
17280
+ */
17281
+ function streamMemberRefusals(block) {
17282
+ const refusals = [];
17283
+ const policy = readStreamPolicy(block);
17284
+ if (policy.kind === "unusable") refusals.push(policy.reason);
17285
+ const tags = readStreamTags(block);
17286
+ if (tags.kind === "unusable") refusals.push(tags.reason);
17287
+ return refusals;
17288
+ }
17289
+ /**
17290
+ * The calls one update (or create) needs against the CURRENT stream arn.
17291
+ *
17292
+ * `freshStream` says the arn the calls will address was minted by THIS
17293
+ * operation — a create, an update-time enable, or the disable / re-enable a
17294
+ * `StreamViewType` change is applied as. A fresh stream holds nothing, so the
17295
+ * previous side is irrelevant there: every readable declared member is
17296
+ * applied, and nothing is deleted. Otherwise the stream is the one the previous
17297
+ * side was applied to, and the plan is the difference. `contentsUnknown` is
17298
+ * the third case: a live stream this operation did NOT mint and the previous
17299
+ * side does not describe (a re-run after a deploy that enabled the stream,
17300
+ * applied a policy, and failed before state recorded any of it). It may hold a
17301
+ * policy, so it is never planned as empty: a policy the desired side does not
17302
+ * declare is deleted, and a declared one is always put.
17303
+ *
17304
+ * On a stream the previous side describes:
17305
+ *
17306
+ * - a member `absent` with a DECLARED previous is a template REMOVAL and is
17307
+ * removed (CloudFormation does the same). Because the rule is symmetric in
17308
+ * its two sides, a rollback — which replays `update()` with the sides SWAPPED
17309
+ * — restores the old policy and tags through the same arms, a view-type
17310
+ * change included (the rollback mints yet another arn, and `freshStream`
17311
+ * applies the old members to it).
17312
+ * - an `unusable` member reaches this plan only from a STATE-borne caller (a
17313
+ * rollback replay, `drift --revert`): the provider refuses it on the template
17314
+ * path before any call. It reports through `onUnusable` and the live setting
17315
+ * is left alone rather than overwritten with a guess: no call on a stream
17316
+ * that stays, and the PREVIOUS member carried over onto a fresh one.
17317
+ * - an `unusable` PREVIOUS tag list cannot say which keys cdkd applied, so a
17318
+ * removal against it untags nothing.
17319
+ *
17320
+ * ORDER is part of the contract, because a resource policy is an access grant
17321
+ * and can condition on `aws:ResourceTag`: `deletePolicy` comes FIRST and
17322
+ * `putPolicy` LAST. A policy that CHANGES together with the tags is therefore
17323
+ * deleted before the first tag call and put back after the last, so neither
17324
+ * the old policy nor the new one is ever evaluated against the other side's
17325
+ * tag set — also when a call in between fails. The price is a window with NO
17326
+ * policy, which drops that policy's `Deny` statements along with its grants;
17327
+ * nothing here is atomic, and that window is the narrower of the two hazards.
17328
+ * An UNCHANGED policy stays in place across a tag-only change: both sides
17329
+ * declare it, and the two tag calls cannot be made one.
17330
+ *
17331
+ * The caller asks only while the DESIRED side has a stream: a disabled stream
17332
+ * is gone and takes both members with it, so nothing addresses its dead arn.
17333
+ */
17334
+ function planStreamMemberOps(desiredBlock, previousBlock, options, onUnusable) {
17335
+ let desiredPolicy = readStreamPolicy(desiredBlock);
17336
+ let desiredTags = readStreamTags(desiredBlock);
17337
+ if (desiredPolicy.kind === "unusable") onUnusable(desiredPolicy.reason);
17338
+ if (desiredTags.kind === "unusable") onUnusable(desiredTags.reason);
17339
+ let previousPolicy = readStreamPolicy(previousBlock);
17340
+ let previousTags = readStreamTags(previousBlock);
17341
+ if (options.freshStream) {
17342
+ if (desiredPolicy.kind === "unusable" && previousPolicy.kind === "usable") desiredPolicy = previousPolicy;
17343
+ if (desiredTags.kind === "unusable" && previousTags.kind === "usable") desiredTags = previousTags;
17344
+ previousPolicy = { kind: "absent" };
17345
+ previousTags = { kind: "absent" };
17346
+ } else if (options.contentsUnknown === true) {
17347
+ previousPolicy = {
17348
+ kind: "unusable",
17349
+ reason: ""
17350
+ };
17351
+ previousTags = { kind: "absent" };
17352
+ }
17353
+ const tagOps = [];
17354
+ if (desiredTags.kind !== "unusable") {
17355
+ const desired = desiredTags.kind === "usable" ? desiredTags.tags : /* @__PURE__ */ new Map();
17356
+ const previous = previousTags.kind === "usable" ? previousTags.tags : /* @__PURE__ */ new Map();
17357
+ const keysToRemove = [...previous.keys()].filter((key) => !desired.has(key));
17358
+ if (keysToRemove.length > 0) tagOps.push({
17359
+ kind: "untag",
17360
+ keys: keysToRemove
17361
+ });
17362
+ const tagsToSet = [...desired].filter(([key, value]) => previous.get(key) !== value).map(([Key, Value]) => ({
17363
+ Key,
17364
+ Value
17365
+ }));
17366
+ if (tagsToSet.length > 0) tagOps.push({
17367
+ kind: "tag",
17368
+ tags: tagsToSet
17369
+ });
17370
+ }
17371
+ const policyPut = desiredPolicy.kind === "usable" && !(previousPolicy.kind === "usable" && samePolicyDocument(previousPolicy.document, desiredPolicy.document));
17372
+ const policyDelete = previousPolicy.kind !== "absent" && (desiredPolicy.kind === "absent" || policyPut && tagOps.length > 0);
17373
+ const ops = [];
17374
+ if (policyDelete) ops.push({ kind: "deletePolicy" });
17375
+ ops.push(...tagOps);
17376
+ if (policyPut && desiredPolicy.kind === "usable") ops.push({
17377
+ kind: "putPolicy",
17378
+ document: desiredPolicy.document
17379
+ });
17380
+ return ops;
17381
+ }
17382
+ /** Two policy JSON strings saying the same thing, whatever their key order. */
17383
+ function samePolicyDocument(left, right) {
17384
+ if (left === right) return true;
17385
+ try {
17386
+ return canonicalJson$1(JSON.parse(left)) === canonicalJson$1(JSON.parse(right));
17387
+ } catch {
17388
+ return false;
17389
+ }
17390
+ }
17391
+ /** The drift read-back gates: one API call each, so each is asked separately. */
17392
+ function streamDeclaresPolicy(desiredBlock) {
17393
+ return readStreamPolicy(desiredBlock).kind === "usable";
17394
+ }
17395
+ /** See {@link streamDeclaresPolicy}. */
17396
+ function streamDeclaresTags(desiredBlock) {
17397
+ return readStreamTags(desiredBlock).kind === "usable";
17398
+ }
17399
+ /**
17400
+ * Re-shape a `GetResourcePolicy` answer into the CFn member, in the DECLARED
17401
+ * spelling of `PolicyDocument`: a template may carry the document as an object
17402
+ * or as a JSON string, and the comparator does not convert between them.
17403
+ * `undefined` when AWS holds no policy.
17404
+ */
17405
+ function reverseMapStreamPolicy(livePolicy, desiredBlock) {
17406
+ if (livePolicy === void 0 || livePolicy === "") return void 0;
17407
+ const declared = blockMembers(blockMembers(desiredBlock)[STREAM_POLICY_KEY])["PolicyDocument"];
17408
+ let parsed;
17409
+ try {
17410
+ parsed = JSON.parse(livePolicy);
17411
+ } catch {
17412
+ return { PolicyDocument: livePolicy };
17413
+ }
17414
+ if (typeof declared !== "string") return { PolicyDocument: parsed };
17415
+ let declaredParsed;
17416
+ try {
17417
+ declaredParsed = JSON.parse(declared);
17418
+ } catch {
17419
+ return { PolicyDocument: livePolicy };
17420
+ }
17421
+ return { PolicyDocument: canonicalJson$1(declaredParsed) === canonicalJson$1(parsed) ? declared : livePolicy };
17422
+ }
17423
+ /**
17424
+ * Does the live policy say what the DECLARED one says? The read-back's
17425
+ * "settled" test: `GetResourcePolicy` is eventually consistent and can answer
17426
+ * with the PREVIOUS policy right after a put, so an answer that differs from
17427
+ * the declaration is re-asked before it is believed.
17428
+ */
17429
+ function streamPolicyMatchesDeclared(livePolicy, desiredBlock) {
17430
+ const declared = readStreamPolicy(desiredBlock);
17431
+ if (declared.kind !== "usable" || livePolicy === void 0) return false;
17432
+ try {
17433
+ return canonicalJson$1(JSON.parse(livePolicy)) === canonicalJson$1(JSON.parse(declared.document));
17434
+ } catch {
17435
+ return false;
17436
+ }
17437
+ }
17438
+ /** The tag twin of {@link streamPolicyMatchesDeclared}, over USER tags. */
17439
+ function streamTagsMatchDeclared(liveTags, desiredBlock) {
17440
+ const declared = readStreamTags(desiredBlock);
17441
+ if (declared.kind !== "usable" || liveTags.length !== declared.tags.size) return false;
17442
+ return liveTags.every((tag) => declared.tags.get(tag.Key) === tag.Value);
17443
+ }
17444
+ /** A key-order-independent serialization. */
17445
+ function canonicalJson$1(value) {
17446
+ if (Array.isArray(value)) return `[${value.map(canonicalJson$1).join(",")}]`;
17447
+ if (isPlainObject$1(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson$1(value[key])}`).join(",")}}`;
17448
+ return JSON.stringify(value) ?? "null";
17449
+ }
17450
+
16946
17451
  //#endregion
16947
17452
  //#region src/provisioning/dynamodb-index-busy-delete.ts
16948
17453
  /**
@@ -18767,16 +19272,10 @@ function warmThroughputAlreadyMatches(desired, live) {
18767
19272
  * to decide with and every block is emitted, cleaned of its AWS-managed
18768
19273
  * members.
18769
19274
  *
18770
- * Known residual, unchanged by this fix: CFn's per-index
18771
- * `ContributorInsightsSpecification` has no `DescribeTable` counterpart (it
18772
- * needs a per-index `DescribeContributorInsights` call), so a template
18773
- * declaring one still cannot converge against a `properties` baseline. It is
18774
- * ALSO dropped on the write side, since `create()` forwards the CFn blob to
18775
- * `CreateTable` and the SDK serializer discards the unknown member — still
18776
- * true after round 6 taught that path to coerce `WarmThroughput`, because the
18777
- * entry is rebuilt with a SPREAD and every other member, known or not, rides
18778
- * along unchanged. Filed as issue
18779
- * [#1782](https://github.com/go-to-k/cdkd/issues/1782).
19275
+ * CFn's per-index `ContributorInsightsSpecification` is NOT mapped here: it
19276
+ * has no `DescribeTable` counterpart, so `readCurrentState` adds it afterwards
19277
+ * from a gated per-index `DescribeContributorInsights` (issue
19278
+ * [#1782](https://github.com/go-to-k/cdkd/issues/1782)).
18780
19279
  */
18781
19280
  function reverseMapSecondaryIndex(live, desired, bagInformative) {
18782
19281
  const out = {};
@@ -19042,6 +19541,12 @@ var DynamoDBTableProvider = class {
19042
19541
  let tableCreated = false;
19043
19542
  try {
19044
19543
  const billingMode = requireConfigString(properties["BillingMode"], "PROVISIONED", "AWS::DynamoDB::Table BillingMode", replayWarn(this.logger, context));
19544
+ if (context?.replayingState !== true) {
19545
+ const [refusal] = contributorInsightsRefusals(properties["ContributorInsightsSpecification"], properties["GlobalSecondaryIndexes"]);
19546
+ if (refusal !== void 0) throw new Error(`${refusal}. Fix the template value`);
19547
+ const [streamRefusal] = streamMemberRefusals(properties["StreamSpecification"]);
19548
+ if (streamRefusal !== void 0) throw new Error(`${streamRefusal}. Fix the template value`);
19549
+ }
19045
19550
  const createParams = {
19046
19551
  TableName: tableName,
19047
19552
  KeySchema: keySchema,
@@ -19066,7 +19571,7 @@ var DynamoDBTableProvider = class {
19066
19571
  };
19067
19572
  if (properties["GlobalSecondaryIndexes"]) {
19068
19573
  const declaredGsis = properties["GlobalSecondaryIndexes"];
19069
- createParams.GlobalSecondaryIndexes = Array.isArray(declaredGsis) ? declaredGsis.map((entry) => this.coerceIndexThroughputForCreate(logicalId, entry, maskSecrets)) : declaredGsis;
19574
+ createParams.GlobalSecondaryIndexes = Array.isArray(declaredGsis) ? declaredGsis.map((entry) => this.coerceIndexThroughputForCreate(logicalId, stripIndexContributorInsights(entry), maskSecrets)) : declaredGsis;
19070
19575
  }
19071
19576
  if (properties["LocalSecondaryIndexes"]) createParams.LocalSecondaryIndexes = properties["LocalSecondaryIndexes"];
19072
19577
  const sse = mapSSESpecification(properties["SSESpecification"]);
@@ -19083,7 +19588,9 @@ var DynamoDBTableProvider = class {
19083
19588
  await this.applyPointInTimeRecovery(tableName, properties["PointInTimeRecoverySpecification"], void 0, maskSecrets);
19084
19589
  await this.applyTimeToLive(tableName, properties["TimeToLiveSpecification"]);
19085
19590
  await this.applyKinesisStreamingDestination(tableName, properties["KinesisStreamSpecification"], void 0, maskSecrets);
19086
- await this.applyContributorInsights(tableName, properties["ContributorInsightsSpecification"]);
19591
+ await this.applyContributorInsights(tableName, properties["ContributorInsightsSpecification"], void 0, maskSecrets);
19592
+ await this.applyIndexContributorInsights(tableName, properties["GlobalSecondaryIndexes"], void 0, maskSecrets);
19593
+ await this.applyStreamMembers(logicalId, resourceType, tableName, tableInfo.streamArn, properties["StreamSpecification"], void 0, true, maskSecrets);
19087
19594
  this.logger.debug(`Successfully created DynamoDB table ${logicalId}: ${tableName}`);
19088
19595
  return {
19089
19596
  physicalId: tableName,
@@ -19119,6 +19626,10 @@ var DynamoDBTableProvider = class {
19119
19626
  const debug = (message) => this.logger.debug(maskSecrets(message));
19120
19627
  debug(`Updating DynamoDB table ${logicalId}: ${maskSecrets(physicalId)}`);
19121
19628
  try {
19629
+ if (context?.replayingState !== true && context?.desiredFromAwsReadback !== true && JSON.stringify(properties["StreamSpecification"]) !== JSON.stringify(previousProperties["StreamSpecification"])) {
19630
+ const [streamRefusal] = streamMemberRefusals(properties["StreamSpecification"]);
19631
+ if (streamRefusal !== void 0) throw new ProvisioningError(`${streamRefusal}. Nothing was applied to DynamoDB table ${logicalId}; fix the template value`, resourceType, logicalId, physicalId);
19632
+ }
19122
19633
  const table = (await this.dynamoDBClient.send(new DescribeTableCommand({ TableName: physicalId }))).Table;
19123
19634
  let latestStreamArn = table?.LatestStreamArn;
19124
19635
  if (table?.TableArn) await this.applyTagDiff(table.TableArn, previousProperties["Tags"], properties["Tags"]);
@@ -19270,6 +19781,10 @@ var DynamoDBTableProvider = class {
19270
19781
  if (JSON.stringify(properties["StreamSpecification"]) !== JSON.stringify(previousProperties["StreamSpecification"])) {
19271
19782
  const newViewType = this.extractStreamViewType(properties["StreamSpecification"]);
19272
19783
  const prevViewType = this.extractStreamViewType(previousProperties["StreamSpecification"]);
19784
+ const liveStream = table?.StreamSpecification;
19785
+ const preUpdateStreamArn = table?.LatestStreamArn;
19786
+ let mintedStream = false;
19787
+ let contentsUnknown = false;
19273
19788
  if (newViewType && prevViewType && newViewType !== prevViewType) {
19274
19789
  await this.dynamoDBClient.send(new UpdateTableCommand({
19275
19790
  TableName: physicalId,
@@ -19284,9 +19799,11 @@ var DynamoDBTableProvider = class {
19284
19799
  }
19285
19800
  }));
19286
19801
  await this.waitForTableActiveAfterUpdate(physicalId);
19287
- latestStreamArn = reenable.TableDescription?.LatestStreamArn ?? await this.describeLatestStreamArn(physicalId);
19802
+ latestStreamArn = await this.resolveMintedStreamArn(physicalId, reenable.TableDescription?.LatestStreamArn, preUpdateStreamArn);
19803
+ mintedStream = true;
19288
19804
  this.logger.debug(`Changed StreamViewType on DynamoDB table ${physicalId} to ${newViewType}`);
19289
- } else if (newViewType) {
19805
+ } else if (newViewType && liveStream?.StreamEnabled === true && liveStream.StreamViewType === newViewType) contentsUnknown = prevViewType !== newViewType;
19806
+ else if (newViewType) {
19290
19807
  const enable = await this.dynamoDBClient.send(new UpdateTableCommand({
19291
19808
  TableName: physicalId,
19292
19809
  StreamSpecification: {
@@ -19295,7 +19812,8 @@ var DynamoDBTableProvider = class {
19295
19812
  }
19296
19813
  }));
19297
19814
  await this.waitForTableActiveAfterUpdate(physicalId);
19298
- latestStreamArn = enable.TableDescription?.LatestStreamArn ?? await this.describeLatestStreamArn(physicalId);
19815
+ latestStreamArn = await this.resolveMintedStreamArn(physicalId, enable.TableDescription?.LatestStreamArn, preUpdateStreamArn);
19816
+ mintedStream = true;
19299
19817
  this.logger.debug(`Enabled DynamoDB Stream on table ${physicalId} (${newViewType})`);
19300
19818
  } else {
19301
19819
  await this.dynamoDBClient.send(new UpdateTableCommand({
@@ -19306,6 +19824,7 @@ var DynamoDBTableProvider = class {
19306
19824
  latestStreamArn = void 0;
19307
19825
  this.logger.debug(`Disabled DynamoDB Stream on table ${physicalId}`);
19308
19826
  }
19827
+ if (newViewType) await this.applyStreamMembers(logicalId, resourceType, physicalId, latestStreamArn, properties["StreamSpecification"], previousProperties["StreamSpecification"], mintedStream, maskSecrets, mintedStream ? preUpdateStreamArn : void 0, contentsUnknown);
19309
19828
  }
19310
19829
  if (JSON.stringify(properties["GlobalSecondaryIndexes"]) !== JSON.stringify(previousProperties["GlobalSecondaryIndexes"])) await this.applyGsiUpdates(physicalId, resourceType, logicalId, previousProperties["GlobalSecondaryIndexes"], properties["GlobalSecondaryIndexes"], properties["AttributeDefinitions"], gsiHandledByBillingFlip, currentLiveIndexNames, currentLiveIndexByName, liveCapacityComparable, effectiveBillingMode === "PAY_PER_REQUEST", maskSecrets);
19311
19830
  if (JSON.stringify(properties["PointInTimeRecoverySpecification"]) !== JSON.stringify(previousProperties["PointInTimeRecoverySpecification"])) await this.applyPointInTimeRecovery(physicalId, properties["PointInTimeRecoverySpecification"], previousProperties["PointInTimeRecoverySpecification"], maskSecrets);
@@ -19318,7 +19837,8 @@ var DynamoDBTableProvider = class {
19318
19837
  await this.applyResourcePolicy(table.TableArn, properties["ResourcePolicy"], previousProperties["ResourcePolicy"]);
19319
19838
  }
19320
19839
  if (JSON.stringify(properties["KinesisStreamSpecification"]) !== JSON.stringify(previousProperties["KinesisStreamSpecification"])) await this.applyKinesisStreamingDestination(physicalId, properties["KinesisStreamSpecification"], previousProperties["KinesisStreamSpecification"], maskSecrets);
19321
- if (JSON.stringify(properties["ContributorInsightsSpecification"]) !== JSON.stringify(previousProperties["ContributorInsightsSpecification"])) await this.applyContributorInsights(physicalId, properties["ContributorInsightsSpecification"], previousProperties["ContributorInsightsSpecification"]);
19840
+ if (JSON.stringify(properties["ContributorInsightsSpecification"]) !== JSON.stringify(previousProperties["ContributorInsightsSpecification"])) await this.applyContributorInsights(physicalId, properties["ContributorInsightsSpecification"], previousProperties["ContributorInsightsSpecification"], maskSecrets);
19841
+ await this.applyIndexContributorInsights(physicalId, properties["GlobalSecondaryIndexes"], previousProperties["GlobalSecondaryIndexes"], maskSecrets);
19322
19842
  return {
19323
19843
  physicalId,
19324
19844
  wasReplaced: false,
@@ -19535,15 +20055,16 @@ var DynamoDBTableProvider = class {
19535
20055
  * Backoff: ~2s,4s,8s,16s,30s,30s... bounded to ~2min total, which comfortably
19536
20056
  * covers the few-second PITR-enable window.
19537
20057
  */
19538
- async retryOnTransientControlPlane(op, label, maxAttempts = 8) {
20058
+ async retryOnTransientControlPlane(op, label, maxAttempts = 8, options = {}) {
19539
20059
  let delayMs = 2e3;
19540
20060
  for (let attempt = 1;; attempt++) try {
19541
20061
  return await op();
19542
20062
  } catch (error) {
19543
20063
  const msg = describeAwsFailure(error).detail;
19544
20064
  const name = error instanceof Error ? error.name : "";
19545
- if (!(/being enabled|being updated|please retry later|backups are being/i.test(msg) || name === "ResourceInUseException" || name === "LimitExceededException") || attempt >= maxAttempts) throw error;
19546
- this.logger.debug(`Transient error on "${label}" (attempt ${attempt}/${maxAttempts}): ${msg} — retrying in ${delayMs}ms`);
20065
+ if (!(/being enabled|being updated|please retry later|backups are being/i.test(msg) || name === "ResourceInUseException" || name === "LimitExceededException" || options.alsoTransient?.(error) === true) || attempt >= maxAttempts) throw error;
20066
+ const retryLine = `Transient error on "${label}" (attempt ${attempt}/${maxAttempts}): ${msg} — retrying in ${delayMs}ms`;
20067
+ this.logger.debug(options.maskSecrets ? options.maskSecrets(retryLine) : retryLine);
19547
20068
  await new Promise((resolve) => setTimeout(resolve, delayMs));
19548
20069
  delayMs = Math.min(delayMs * 2, 3e4);
19549
20070
  }
@@ -19719,6 +20240,90 @@ var DynamoDBTableProvider = class {
19719
20240
  return typeof viewType === "string" && viewType.length > 0 ? viewType : void 0;
19720
20241
  }
19721
20242
  /**
20243
+ * Apply `StreamSpecification.ResourcePolicy` / `.Tags` (issue
20244
+ * [#3458](https://github.com/go-to-k/cdkd/issues/3458)) against the STREAM
20245
+ * arn. Neither is a member of the SDK's `StreamSpecification`, so until this
20246
+ * existed both were dropped on `CreateTable` / `UpdateTable`: the deploy was
20247
+ * green and the stream had no policy.
20248
+ *
20249
+ * `streamArn` must be the table's CURRENT `LatestStreamArn` — after a
20250
+ * `StreamViewType` change that is the arn the re-enable minted, never the
20251
+ * recorded one. What to call, in which order, and the removal / rollback /
20252
+ * unreadable-member rules live on {@link planStreamMemberOps}.
20253
+ *
20254
+ * A plan with calls to make and no arn to make them against — or, when this
20255
+ * operation minted the stream, only the `deadStreamArn` read before it —
20256
+ * fails LOUDLY: a
20257
+ * silent skip would record the policy in state as applied, the next deploy
20258
+ * would see no difference, and the grant would stay missing for good.
20259
+ *
20260
+ * The policy text is never logged: it names principals and can carry a
20261
+ * resolved secret in a condition value.
20262
+ */
20263
+ async applyStreamMembers(logicalId, resourceType, tableName, streamArn, desiredBlock, previousBlock, freshStream, maskSecrets, deadStreamArn, contentsUnknown = false) {
20264
+ const warn = (message) => this.logger.warn(maskSecrets(message));
20265
+ const debug = (message) => this.logger.debug(maskSecrets(message));
20266
+ const ops = planStreamMemberOps(desiredBlock, previousBlock, {
20267
+ freshStream,
20268
+ contentsUnknown
20269
+ }, (reason) => warn(`DynamoDB table ${tableName}: ${reason}, so that member of the stream was left as it is. The value came from a recorded state, which a template edit cannot reach.`));
20270
+ if (ops.length === 0) return;
20271
+ if (streamArn === void 0 || streamArn === "" || streamArn === deadStreamArn) throw new ProvisioningError(`Cannot apply StreamSpecification.ResourcePolicy / Tags for DynamoDB table ${logicalId}: DescribeTable returned no ${deadStreamArn === void 0 ? "" : "new "}LatestStreamArn`, resourceType, logicalId, tableName);
20272
+ for (const op of ops) {
20273
+ await this.sendStreamMemberOp(streamArn, op, freshStream, maskSecrets);
20274
+ debug(`Applied ${op.kind} to the stream of DynamoDB table ${tableName}`);
20275
+ }
20276
+ }
20277
+ /** Issue ONE {@link StreamMemberOp} against the stream arn. */
20278
+ async sendStreamMemberOp(streamArn, op, freshStream, maskSecrets) {
20279
+ const target = maskSecrets(streamArn);
20280
+ const retryOptions = {
20281
+ maskSecrets,
20282
+ ...freshStream ? { alsoTransient: (error) => error instanceof ResourceNotFoundException$1 } : {}
20283
+ };
20284
+ const send = (call, label) => this.retryOnTransientControlPlane(call, label, void 0, retryOptions);
20285
+ switch (op.kind) {
20286
+ case "deletePolicy":
20287
+ try {
20288
+ await send(() => this.dynamoDBClient.send(new DeleteResourcePolicyCommand({ ResourceArn: streamArn })), `delete the stream ResourcePolicy of ${target}`);
20289
+ } catch (error) {
20290
+ if (!(error instanceof PolicyNotFoundException)) throw error;
20291
+ }
20292
+ return;
20293
+ case "untag":
20294
+ await send(() => this.dynamoDBClient.send(new UntagResourceCommand$2({
20295
+ ResourceArn: streamArn,
20296
+ TagKeys: op.keys
20297
+ })), `untag the stream ${target}`);
20298
+ return;
20299
+ case "tag":
20300
+ await send(() => this.dynamoDBClient.send(new TagResourceCommand$2({
20301
+ ResourceArn: streamArn,
20302
+ Tags: op.tags
20303
+ })), `tag the stream ${target}`);
20304
+ return;
20305
+ case "putPolicy":
20306
+ await send(() => this.dynamoDBClient.send(new PutResourcePolicyCommand({
20307
+ ResourceArn: streamArn,
20308
+ Policy: op.document
20309
+ })), `put the stream ResourcePolicy of ${target}`);
20310
+ return;
20311
+ }
20312
+ }
20313
+ /**
20314
+ * The arn of the stream an enabling `UpdateTable` just minted.
20315
+ *
20316
+ * The arn that call echoes is believed only when it DIFFERS from the one read
20317
+ * before the update: DynamoDB keeps reporting a disabled stream's arn as
20318
+ * `LatestStreamArn`, so an echo equal to it (or no echo) is re-read from
20319
+ * `DescribeTable` after the ACTIVE wait. Shared by the enable and the
20320
+ * view-type arms — a table whose stream was disabled by an earlier deploy is
20321
+ * in the same state as one mid view-type change.
20322
+ */
20323
+ async resolveMintedStreamArn(tableName, echoedArn, preUpdateArn) {
20324
+ return echoedArn !== void 0 && echoedArn !== preUpdateArn ? echoedArn : this.describeLatestStreamArn(tableName);
20325
+ }
20326
+ /**
19722
20327
  * Fetch the table's current `LatestStreamArn` via DescribeTable. Used as a
19723
20328
  * fallback when an UpdateTable that enabled a stream did not echo the ARN
19724
20329
  * back in its TableDescription, so `Fn::GetAtt [Table, StreamArn]` still
@@ -19734,24 +20339,53 @@ var DynamoDBTableProvider = class {
19734
20339
  *
19735
20340
  * Called from both `create()` (after the table is ACTIVE) and `update()`
19736
20341
  * (only when the value changed). On `update()`-side removal — template drops
19737
- * the block but it was present before — insights is disabled.
20342
+ * the block but it was present before — insights is disabled. The block is
20343
+ * read by the reader the per-index twin uses
20344
+ * ({@link applyIndexContributorInsights}), so the two cannot disagree about
20345
+ * one shape: an unreadable block is announced and SKIPPED rather than read as
20346
+ * a default (`Boolean('false')` used to ENABLE what the template disabled).
19738
20347
  */
19739
- async applyContributorInsights(tableName, spec, previousSpec) {
19740
- let action;
19741
- let mode;
19742
- if (spec !== void 0 && spec !== null) {
19743
- const s = spec;
19744
- const enabled = Boolean(s["Enabled"]);
19745
- action = enabled ? "ENABLE" : "DISABLE";
19746
- if (enabled && s["Mode"] !== void 0) mode = s["Mode"];
19747
- } else if (previousSpec !== void 0 && previousSpec !== null) action = "DISABLE";
19748
- if (action === void 0) return;
20348
+ async applyContributorInsights(tableName, spec, previousSpec, maskSecrets) {
20349
+ const warn = (message) => this.logger.warn(maskSecrets(message));
20350
+ const op = planContributorInsightsOp(readContributorInsightsSpec(spec, CONTRIBUTOR_INSIGHTS_KEY), readContributorInsightsSpec(previousSpec, CONTRIBUTOR_INSIGHTS_KEY), (reason) => warn(`DynamoDB table ${tableName}: ${reason}, so Contributor Insights was left as it is. Fix the template value and re-deploy.`));
20351
+ if (op === void 0) return;
20352
+ await this.sendContributorInsightsOp(tableName, op, maskSecrets);
20353
+ }
20354
+ /**
20355
+ * Apply every per-index `ContributorInsightsSpecification`
20356
+ * (`GlobalSecondaryIndexes[].ContributorInsightsSpecification`, issue
20357
+ * [#1782](https://github.com/go-to-k/cdkd/issues/1782)) — the table-level
20358
+ * call with `IndexName` added, one call per index whose block changed.
20359
+ *
20360
+ * The block is not a member of the SDK's `GlobalSecondaryIndex`, so until
20361
+ * this existed it was dropped by the serializer on `CreateTable` and never
20362
+ * sent by `applyGsiUpdates`: the deploy was green and the feature was off.
20363
+ *
20364
+ * `UpdateContributorInsights` answers `ResourceNotFoundException` for an
20365
+ * index that is not ACTIVE yet, which {@link retryOnTransientControlPlane}
20366
+ * does not retry, so the index wait runs FIRST — and only when there is a
20367
+ * call to make, so an ordinary update pays no extra `DescribeTable`.
20368
+ * `previousIndexes` is `undefined` on `create()`. Removal and rollback
20369
+ * semantics live on {@link planContributorInsightsOp}.
20370
+ */
20371
+ async applyIndexContributorInsights(tableName, desiredIndexes, previousIndexes, maskSecrets) {
20372
+ const warn = (message) => this.logger.warn(maskSecrets(message));
20373
+ const ops = planIndexContributorInsightsOps(desiredIndexes, previousIndexes, (indexName, reason) => warn(`${this.indexScopeAt(indexName, tableName, maskSecrets)}: ${reason}, so its Contributor Insights was left as it is. Fix the template value and re-deploy.`));
20374
+ if (ops.length === 0) return;
20375
+ await this.waitForTableAndIndexesActive(tableName);
20376
+ for (const op of ops) await this.sendContributorInsightsOp(tableName, op, maskSecrets);
20377
+ }
20378
+ /** Issue ONE `UpdateContributorInsights`, against the table or one index. */
20379
+ async sendContributorInsightsOp(tableName, op, maskSecrets) {
20380
+ const debug = (message) => this.logger.debug(maskSecrets(message));
20381
+ const target = op.indexName === void 0 ? `DynamoDB table ${tableName}` : this.indexScopeAt(op.indexName, tableName, maskSecrets);
19749
20382
  await this.retryOnTransientControlPlane(() => this.dynamoDBClient.send(new UpdateContributorInsightsCommand({
19750
20383
  TableName: tableName,
19751
- ContributorInsightsAction: action,
19752
- ...mode ? { ContributorInsightsMode: mode } : {}
19753
- })), `set ContributorInsights on ${tableName}`);
19754
- this.logger.debug(`Set ContributorInsightsAction=${action}${mode !== void 0 ? ` Mode=${mode}` : ""} on DynamoDB table ${tableName}`);
20384
+ ...op.indexName !== void 0 ? { IndexName: op.indexName } : {},
20385
+ ContributorInsightsAction: op.action,
20386
+ ...op.mode !== void 0 ? { ContributorInsightsMode: op.mode } : {}
20387
+ })), `set ContributorInsights on ${target}`);
20388
+ debug(`Set ContributorInsightsAction=${op.action}${op.mode !== void 0 ? ` Mode=${op.mode}` : ""} on ${target}`);
19755
20389
  }
19756
20390
  /**
19757
20391
  * Poll DescribeTable until the table reaches ACTIVE status
@@ -20732,32 +21366,35 @@ var DynamoDBTableProvider = class {
20732
21366
  * level: it is order-SIGNIFICANT (HASH before RANGE), so sorting it would
20733
21367
  * HIDE a real key change rather than remove a phantom one.
20734
21368
  *
20735
- * `GlobalSecondaryIndexes` / `LocalSecondaryIndexes` are NOT declared either,
20736
- * and that is a MECHANISM limit rather than a judgement about the lists —
20737
- * both really are sets keyed by `IndexName` (issue #1767 proposes declaring
20738
- * them). Every entry here is a SUBTREE declaration, and unlike
20739
- * `getDriftUnknownPaths` this walk DESCENDS INTO ARRAY ELEMENTS giving each
20740
- * the parent's path (`drift-normalize.ts`), so a `'GlobalSecondaryIndexes'`
20741
- * entry also reaches `GlobalSecondaryIndexes.KeySchema` and would sort the
20742
- * per-index key schema — reversing the sentence above at the index level
20743
- * only. Issue #1767 calls the sort and the member reverse-map separable;
20744
- * this change ships the reverse-map, and the ordering half needed a
20745
- * leaf-only form of the declaration first (issue
20746
- * [#1783](https://github.com/go-to-k/cdkd/issues/1783)). **That form now
20747
- * EXISTS** — `LEAF_ONLY_PATH_SUFFIX` (`[]`) landed in
20748
- * [#1799](https://github.com/go-to-k/cdkd/pull/1799), so
20749
- * `'GlobalSecondaryIndexes[]'` would claim the list alone without reaching
20750
- * the per-index `KeySchema`. It is deliberately NOT declared here yet:
20751
- * adopting it is its own change with its own real-AWS verification, tracked
20752
- * as issue [#1812](https://github.com/go-to-k/cdkd/issues/1812). Consequence
20753
- * until then, stated so it is not mistaken for solved: an index list AWS
20754
- * returns in a different ORDER than the template declared is still phantom
20755
- * drift against a `properties` baseline. It is not reachable on the ordinary
20756
- * `observedProperties` path, where both sides come from this same readback.
21369
+ * `GlobalSecondaryIndexes` / `LocalSecondaryIndexes` ARE declared, in the
21370
+ * LEAF-ONLY form (`LEAF_ONLY_PATH_SUFFIX`, `[]`, from
21371
+ * [#1799](https://github.com/go-to-k/cdkd/pull/1799)): both are sets keyed
21372
+ * by `IndexName`, and `DescribeTable` does not return them in the template's
21373
+ * order. A plain SUBTREE entry would be wrong — the unordered walk descends
21374
+ * into array elements giving each the parent's path (`drift-normalize.ts`),
21375
+ * so `'GlobalSecondaryIndexes'` would also sort each index's `KeySchema` and
21376
+ * reverse the sentence above at the index level. The leaf-only form sorts
21377
+ * the list and stops there.
21378
+ *
21379
+ * Measured on real AWS by issue
21380
+ * [#1782](https://github.com/go-to-k/cdkd/issues/1782)'s two-index fixture:
21381
+ * a table created with `[giA, giB]` read back as `[giB, giA]`, so a
21382
+ * `properties` baseline reported whole-list drift although every member
21383
+ * round-tripped. The phantom pre-dates that issue — the #1767 arm only ever
21384
+ * had ONE index — and was not reachable against an `observedProperties`
21385
+ * baseline, where both sides come from the same readback. Both comparison
21386
+ * sides are sorted, by a key-order-independent serialization of the whole
21387
+ * entry, so a baseline captured in AWS's order by an earlier binary keeps
21388
+ * converging; a REAL difference still reports the list, since entries that
21389
+ * differ cannot be made equal by reordering them.
20757
21390
  */
20758
21391
  getDriftUnorderedPaths(resourceType) {
20759
21392
  if (resourceType !== "AWS::DynamoDB::Table") return [];
20760
- return ["AttributeDefinitions"];
21393
+ return [
21394
+ "AttributeDefinitions",
21395
+ "GlobalSecondaryIndexes[]",
21396
+ "LocalSecondaryIndexes[]"
21397
+ ];
20761
21398
  }
20762
21399
  /**
20763
21400
  * `WarmThroughput` is reported by AWS for EVERY table, whether or not the
@@ -20873,9 +21510,12 @@ var DynamoDBTableProvider = class {
20873
21510
  * shape differences are wrapping:
20874
21511
  * - BillingMode lives under `BillingModeSummary.BillingMode` in the API
20875
21512
  * response, but the CFn property is a flat `BillingMode` string.
20876
- * - StreamSpecification's CFn shape includes only `StreamViewType`; the
20877
- * API response carries `StreamEnabled` too. We surface both since the
20878
- * drift comparator only descends into keys present in state.
21513
+ * - StreamSpecification's CFn shape is `StreamViewType`, `ResourcePolicy`
21514
+ * and `Tags`; the API response carries `StreamEnabled` and
21515
+ * `StreamViewType` only. `StreamEnabled` is surfaced too, since the
21516
+ * comparator descends a `properties` baseline by the keys state holds.
21517
+ * `ResourcePolicy` / `Tags` (issue #3458) live on the STREAM arn and are
21518
+ * read from it, each only when the desired block declares that member.
20879
21519
  * - GSI / LSI in the API response include `IndexStatus`, `Backfilling`,
20880
21520
  * `ItemCount`, `IndexArn` and sizing fields cdkd never sets. They used to
20881
21521
  * be forwarded VERBATIM under a comment claiming "the comparator filters
@@ -20902,7 +21542,9 @@ var DynamoDBTableProvider = class {
20902
21542
  * caller passes). It is consulted for the table-level `WarmThroughput` — see
20903
21543
  * {@link getDriftUnknownPaths} for the measurement and the reasoning — and,
20904
21544
  * since issue #1767, for the per-index throughput blocks of each secondary
20905
- * index, matched by `IndexName`.
21545
+ * index, matched by `IndexName`. Since issue #1782 it also gates the per-index
21546
+ * `ContributorInsightsSpecification` read, which costs one
21547
+ * `DescribeContributorInsights` per declaring index.
20906
21548
  */
20907
21549
  async readCurrentState(physicalId, _logicalId, _resourceType, properties) {
20908
21550
  try {
@@ -20929,10 +21571,14 @@ var DynamoDBTableProvider = class {
20929
21571
  if (table.WarmThroughput.WriteUnitsPerSecond !== void 0) wt["WriteUnitsPerSecond"] = table.WarmThroughput.WriteUnitsPerSecond;
20930
21572
  if (Object.keys(wt).length > 0) result["WarmThroughput"] = wt;
20931
21573
  }
20932
- if (table.StreamSpecification?.StreamEnabled && table.StreamSpecification.StreamViewType) result["StreamSpecification"] = {
20933
- StreamEnabled: true,
20934
- StreamViewType: table.StreamSpecification.StreamViewType
20935
- };
21574
+ if (table.StreamSpecification?.StreamEnabled && table.StreamSpecification.StreamViewType) {
21575
+ const streamBlock = {
21576
+ StreamEnabled: true,
21577
+ StreamViewType: table.StreamSpecification.StreamViewType
21578
+ };
21579
+ result["StreamSpecification"] = streamBlock;
21580
+ await this.readStreamMembers(physicalId, table.LatestStreamArn, properties?.["StreamSpecification"], streamBlock);
21581
+ }
20936
21582
  const bagInformative = desiredBagIsInformative(properties);
20937
21583
  if (table.GlobalSecondaryIndexes && table.GlobalSecondaryIndexes.length > 0) {
20938
21584
  const desiredByName = desiredIndexEntriesByName(properties?.["GlobalSecondaryIndexes"]);
@@ -21001,15 +21647,31 @@ var DynamoDBTableProvider = class {
21001
21647
  }
21002
21648
  try {
21003
21649
  const ciResp = await this.dynamoDBClient.send(new DescribeContributorInsightsCommand({ TableName: physicalId }));
21004
- const status = ciResp.ContributorInsightsStatus;
21005
- if (status === "ENABLED" || status === "DISABLED") {
21006
- const cspec = { Enabled: status === "ENABLED" };
21007
- if (status === "ENABLED" && ciResp.ContributorInsightsMode !== void 0) cspec["Mode"] = ciResp.ContributorInsightsMode;
21008
- result["ContributorInsightsSpecification"] = cspec;
21009
- }
21650
+ const cspec = reverseMapTableContributorInsights(ciResp.ContributorInsightsStatus, ciResp.ContributorInsightsMode);
21651
+ if (cspec !== void 0) result["ContributorInsightsSpecification"] = cspec;
21010
21652
  } catch (err) {
21011
21653
  this.logger.debug(`Could not read ContributorInsights for ${physicalId}: ${describeAwsFailure(err).detail}`);
21012
21654
  }
21655
+ const emittedGsis = result["GlobalSecondaryIndexes"];
21656
+ if (bagInformative && Array.isArray(emittedGsis)) {
21657
+ const desiredByName = desiredIndexEntriesByName(properties?.["GlobalSecondaryIndexes"]);
21658
+ for (const emitted of emittedGsis) {
21659
+ const indexName = emitted["IndexName"];
21660
+ if (typeof indexName !== "string") continue;
21661
+ const desiredEntry = desiredByName.get(indexName);
21662
+ if (!indexDeclaresContributorInsights(desiredEntry)) continue;
21663
+ try {
21664
+ const indexResp = await this.dynamoDBClient.send(new DescribeContributorInsightsCommand({
21665
+ TableName: physicalId,
21666
+ IndexName: indexName
21667
+ }));
21668
+ const block = reverseMapIndexContributorInsights(indexResp.ContributorInsightsStatus, indexResp.ContributorInsightsMode, desiredEntry?.[CONTRIBUTOR_INSIGHTS_KEY]);
21669
+ if (block !== void 0) emitted[CONTRIBUTOR_INSIGHTS_KEY] = block;
21670
+ } catch (err) {
21671
+ this.logger.debug(`Could not read ContributorInsights for index ${indexName} of ${physicalId}: ${describeAwsFailure(err).detail}`);
21672
+ }
21673
+ }
21674
+ }
21013
21675
  return result;
21014
21676
  } catch (err) {
21015
21677
  if (err instanceof ResourceNotFoundException$1) return void 0;
@@ -21017,6 +21679,92 @@ var DynamoDBTableProvider = class {
21017
21679
  }
21018
21680
  }
21019
21681
  /**
21682
+ * How long an eventually-consistent stream-member read that does NOT match
21683
+ * the declaration is re-asked before it is believed: one entry per extra
21684
+ * attempt.
21685
+ */
21686
+ streamMemberRereadDelaysMs = [
21687
+ 500,
21688
+ 1e3,
21689
+ 2e3
21690
+ ];
21691
+ /** Run `read`, re-asking after each configured delay until `settled` accepts the answer. */
21692
+ async reAskUntilSettled(read, settled) {
21693
+ let answer = await read();
21694
+ for (const delayMs of this.streamMemberRereadDelaysMs) {
21695
+ if (settled(answer)) break;
21696
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
21697
+ answer = await read();
21698
+ }
21699
+ return answer;
21700
+ }
21701
+ /**
21702
+ * Read `StreamSpecification.ResourcePolicy` / `.Tags` (issue #3458) off the
21703
+ * table's CURRENT stream arn into `streamBlock`.
21704
+ *
21705
+ * One API call per member, so each is GATED on the desired block declaring a
21706
+ * member cdkd would send — the same reader the write plan runs. The gate is
21707
+ * also what keeps an undeclared member out of an `observedProperties`
21708
+ * baseline, which is compared by the UNION of both sides' keys.
21709
+ *
21710
+ * No policy (`PolicyNotFoundException`) and an empty user tag set emit
21711
+ * NOTHING: that is "absent" in the template's own spelling, which a
21712
+ * declared member then reports as drift (a declared EMPTY list is the one
21713
+ * exception, and reads back as the empty list it is). `aws:`-prefixed system tags are
21714
+ * dropped like the table-level ones, and the list needs no unordered path —
21715
+ * the comparator sorts every `{Key, ...}` list on both sides. Any other
21716
+ * failed read omits the member and says why at debug level, like the sibling
21717
+ * reads of this method.
21718
+ *
21719
+ * Both reads are EVENTUALLY consistent — right after a write they can answer
21720
+ * "absent" or with the PREVIOUS value (their command docs say so) — and the
21721
+ * deploy captures `observedProperties` right after the writes. So an answer
21722
+ * that does not match the DECLARED member is re-asked a few times before it
21723
+ * is believed, or a lagging read would freeze a baseline the settled stream
21724
+ * can never equal. A member that really drifted costs those few seconds per
21725
+ * drift run.
21726
+ *
21727
+ * `cdkd drift --revert` replays through `update()`, where the read-back is
21728
+ * the PREVIOUS side and {@link planStreamMemberOps} derives the restore.
21729
+ */
21730
+ async readStreamMembers(tableName, streamArn, desiredBlock, streamBlock) {
21731
+ if (streamArn === void 0 || streamArn === "") return;
21732
+ if (streamDeclaresPolicy(desiredBlock)) try {
21733
+ const livePolicy = await this.reAskUntilSettled(async () => {
21734
+ try {
21735
+ return (await this.dynamoDBClient.send(new GetResourcePolicyCommand({ ResourceArn: streamArn }))).Policy;
21736
+ } catch (err) {
21737
+ if (err instanceof PolicyNotFoundException) return void 0;
21738
+ throw err;
21739
+ }
21740
+ }, (answer) => streamPolicyMatchesDeclared(answer, desiredBlock));
21741
+ const member = reverseMapStreamPolicy(livePolicy, desiredBlock);
21742
+ if (member !== void 0) streamBlock[STREAM_POLICY_KEY] = member;
21743
+ } catch (err) {
21744
+ this.logger.debug(`Could not read the stream ResourcePolicy for ${tableName}: ${describeAwsFailure(err).detail}`);
21745
+ }
21746
+ if (streamDeclaresTags(desiredBlock)) try {
21747
+ const listOnce = async () => {
21748
+ const liveTags = [];
21749
+ let nextToken;
21750
+ do {
21751
+ const response = await this.dynamoDBClient.send(new ListTagsOfResourceCommand({
21752
+ ResourceArn: streamArn,
21753
+ NextToken: nextToken
21754
+ }));
21755
+ liveTags.push(...response.Tags ?? []);
21756
+ nextToken = response.NextToken;
21757
+ } while (nextToken !== void 0 && nextToken !== "");
21758
+ return normalizeAwsTagsToCfn(liveTags);
21759
+ };
21760
+ const tags = await this.reAskUntilSettled(listOnce, (answer) => streamTagsMatchDeclared(answer, desiredBlock));
21761
+ const declared = desiredBlock[STREAM_TAGS_KEY];
21762
+ if (tags.length > 0 || Array.isArray(declared) && declared.length === 0) streamBlock[STREAM_TAGS_KEY] = tags;
21763
+ } catch (err) {
21764
+ this.logger.debug(`Could not read the stream Tags for ${tableName}: ${describeAwsFailure(err).detail}`);
21765
+ }
21766
+ }
21767
+ /**
21020
21768
  * Adopt an existing DynamoDB table into cdkd state.
21021
21769
  *
21022
21770
  * Lookup order:
@@ -84981,8 +85729,394 @@ function createForceUnlockCommand() {
84981
85729
  return cmd;
84982
85730
  }
84983
85731
 
85732
+ //#endregion
85733
+ //#region src/cli/commands/import-deployed-parameters.ts
85734
+ /**
85735
+ * UNVERIFIABLE INPUTS for `cdkd import`'s observed-baseline refusal (issue
85736
+ * [#2854](https://github.com/go-to-k/cdkd/issues/2854)).
85737
+ *
85738
+ * `cdkd import` accepts no parameter values, so every template parameter is
85739
+ * bound to its `Default`. When the resources were deployed by a CloudFormation
85740
+ * stack, the value CloudFormation actually used can differ — and the ordinary
85741
+ * secret-carrying shape is a placeholder `Default` (`CHANGEME`) over a DEPLOYED
85742
+ * value of `{{resolve:secretsmanager:...}}`. The persisted `properties` then
85743
+ * spell no reference anywhere, the position-based redaction of the observed
85744
+ * capture has no evidence, and the decrypted readback would be persisted.
85745
+ *
85746
+ * The remedy here is deliberately PREMISE-INDEPENDENT. It never looks for a
85747
+ * `{{resolve:` opener in what `DescribeStacks` returns (AWS documents
85748
+ * `ParameterValue` as "the input value", which says the reference comes back
85749
+ * literally, but nothing in this module relies on it). It asks one question —
85750
+ * "is the deployed value PROVABLY the value this import bound?" — and treats
85751
+ * every other answer as divergent: a literal reference, the `****` a `NoEcho`
85752
+ * parameter is masked to, a value AWS resolved, a parameter missing from the
85753
+ * response, a `DescribeStacks` that failed. All of those differ from the bound
85754
+ * placeholder, so all of them refuse.
85755
+ *
85756
+ * THE DEPLOYED VALUES ARE COMPARISON-ONLY. They are never bound into the
85757
+ * resolve, never logged, never put in an error message, and never persisted:
85758
+ * binding them is the one design that would leak if AWS ever returned a
85759
+ * resolved value. {@link DeployedParameters} holds them in a `#private` field so
85760
+ * that a stray `JSON.stringify` / `util.inspect` / template literal of the
85761
+ * object renders nothing, and an SDK failure is reported by its error NAME
85762
+ * alone, because an SDK message can echo request input.
85763
+ *
85764
+ * TAINT THROUGH ANOTHER RESOURCE'S ATTRIBUTES IS FOLLOWED within one stack: the
85765
+ * walk records the target of every `Fn::GetAtt` / `${A.Attr}`, and the caller
85766
+ * closes the refused set over those targets, so a resource reading an attribute
85767
+ * of a refused resource (`{Fn::GetAtt: [SsmParam, Value]}` into a Lambda
85768
+ * environment — what CDK's `param.stringValue` emits) is refused with it. A
85769
+ * plain `Ref` of a resource is NOT followed: it yields the physical id, and a
85770
+ * secret used as a resource NAME is already disclosed by the identifier itself.
85771
+ *
85772
+ * KNOWN RESIDUAL, not followed: taint ACROSS the nested-stack boundary. A
85773
+ * parent property reading `Fn::GetAtt: [Child, Outputs.X]`, where the child's
85774
+ * output derives from a divergent CHILD parameter, is judged on the parent's
85775
+ * own parameters only — the root is resolved before any child is read.
85776
+ */
85777
+ /**
85778
+ * What CloudFormation reports a stack was deployed with. Opaque on purpose:
85779
+ * the only question it answers is {@link DeployedParameters.provablyEquals}.
85780
+ */
85781
+ var DeployedParameters = class DeployedParameters {
85782
+ #entries;
85783
+ constructor(entries) {
85784
+ this.#entries = entries;
85785
+ }
85786
+ /** `DescribeStacks` answered; `entries` is its `Parameters` list. */
85787
+ static fromDescribeStacks(parameters) {
85788
+ const entries = /* @__PURE__ */ new Map();
85789
+ for (const parameter of parameters) {
85790
+ if (typeof parameter.ParameterKey !== "string") continue;
85791
+ entries.set(parameter.ParameterKey, {
85792
+ value: parameter.ParameterValue,
85793
+ resolvedValue: parameter.ResolvedValue
85794
+ });
85795
+ }
85796
+ return new DeployedParameters(entries);
85797
+ }
85798
+ /** The deployed values could not be read: NOTHING is provable. */
85799
+ static unavailable() {
85800
+ return new DeployedParameters(void 0);
85801
+ }
85802
+ /**
85803
+ * Whether the deployed value of `name` is provably the value the import
85804
+ * BOUND. The deployed string goes through the SAME coercion the resolver
85805
+ * applied to the bound one (`Number`, `CommaDelimitedList`, `List<...>`), so
85806
+ * `'1'` equals a bound `1` and `'a, b'` equals a bound `['a', 'b']`. For an
85807
+ * SSM-typed parameter the resolver binds the RESOLVED value, so that is the
85808
+ * side compared — `ParameterValue` is only the key there.
85809
+ */
85810
+ provablyEquals(name, type, bound) {
85811
+ const entry = this.#entries?.get(name);
85812
+ if (entry === void 0 || typeof type !== "string") return false;
85813
+ const ssmType = type.startsWith("AWS::SSM::Parameter::Value");
85814
+ const deployed = ssmType ? entry.resolvedValue : entry.value;
85815
+ if (typeof deployed !== "string") return false;
85816
+ const comparedType = ssmType ? ssmResolvedValueType(type) : type;
85817
+ return stableEquals(comparedType === void 0 ? deployed : coerceParameterTypedValue(deployed, comparedType), bound);
85818
+ }
85819
+ };
85820
+ /** Structural equality for the scalar / scalar-array values a parameter binds to. */
85821
+ function stableEquals(a, b) {
85822
+ if (Array.isArray(a) || Array.isArray(b)) {
85823
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
85824
+ return a.every((element, index) => stableEquals(element, b[index]));
85825
+ }
85826
+ if (typeof a === "string" && typeof b === "number") return Number.isFinite(b) && a === String(b);
85827
+ if (typeof a === "number" || typeof b === "number") return typeof a === "number" && typeof b === "number" && Number.isFinite(a) && a === b;
85828
+ if (typeof a === "string" && typeof b === "boolean") return a === String(b);
85829
+ return typeof a === "string" && typeof b === "string" && a === b;
85830
+ }
85831
+ async function readDeployedParameters(cfnStackName, cfnClient, logger, displayName, options) {
85832
+ try {
85833
+ const stack = (await cfnClient.send(new DescribeStacksCommand({ StackName: cfnStackName }))).Stacks?.[0];
85834
+ if (!stack) {
85835
+ logger.warn(unavailableWarning(displayName, "the stack was not returned"));
85836
+ return DeployedParameters.unavailable();
85837
+ }
85838
+ return DeployedParameters.fromDescribeStacks(stack.Parameters ?? []);
85839
+ } catch (err) {
85840
+ if (options?.absentStackIsNoSource === true && err instanceof Error && err.name === "ValidationError" && /does not exist/i.test(err.message)) return;
85841
+ const name = err instanceof Error && /^[A-Za-z0-9_.]{1,80}$/.test(err.name) ? err.name : "error";
85842
+ logger.warn(unavailableWarning(displayName, name));
85843
+ return DeployedParameters.unavailable();
85844
+ }
85845
+ }
85846
+ function unavailableWarning(displayName, reason) {
85847
+ return `Could not read the deployed parameter values of CloudFormation stack '${displayName}' (${reason}). 'cdkd import' needs the cloudformation:DescribeStacks permission to prove that a template parameter was deployed with its 'Default'; without it every resource that depends on a template parameter is imported WITHOUT an observed drift baseline. Review 'cdkd diff' before the next 'cdkd deploy'.`;
85848
+ }
85849
+ /**
85850
+ * The declared parameters whose deployed value is NOT provably the bound one.
85851
+ * A parameter the import could not bind at all (no `Default`, an unreferenced
85852
+ * or failed SSM default, the empty fallback bag) is divergent by definition.
85853
+ */
85854
+ function divergentParameterNames(template, bound, deployed) {
85855
+ const divergent = /* @__PURE__ */ new Set();
85856
+ const declared = template.Parameters;
85857
+ if (declared === void 0 || declared === null || typeof declared !== "object") return divergent;
85858
+ for (const [name, definition] of Object.entries(declared)) {
85859
+ const record = definition !== null && typeof definition === "object" ? definition : void 0;
85860
+ const type = record?.["Type"];
85861
+ const noEcho = record?.["NoEcho"];
85862
+ if (noEcho === true || typeof noEcho === "string" && noEcho.toLowerCase() === "true") {
85863
+ divergent.add(name);
85864
+ continue;
85865
+ }
85866
+ if (!Object.hasOwn(bound, name) || !deployed.provablyEquals(name, type, bound[name])) divergent.add(name);
85867
+ }
85868
+ return divergent;
85869
+ }
85870
+ /**
85871
+ * Every intrinsic the dependence walk knows how to read. An `Fn::`-prefixed
85872
+ * key OUTSIDE this list is unclassifiable: it may name a parameter in a way
85873
+ * the generic recursion cannot see, so the walk fails closed on it.
85874
+ */
85875
+ const KNOWN_INTRINSICS = /* @__PURE__ */ new Set([
85876
+ "Fn::GetAtt",
85877
+ "Fn::Join",
85878
+ "Fn::Sub",
85879
+ "Fn::Select",
85880
+ "Fn::Split",
85881
+ "Fn::If",
85882
+ "Fn::Equals",
85883
+ "Fn::And",
85884
+ "Fn::Or",
85885
+ "Fn::Not",
85886
+ "Fn::ImportValue",
85887
+ "Fn::GetStackOutput",
85888
+ "Fn::FindInMap",
85889
+ "Fn::Base64",
85890
+ "Fn::GetAZs",
85891
+ "Fn::Cidr"
85892
+ ]);
85893
+ /** `${Name}` / `${Name.Attr}` in an `Fn::Sub` string; `${!Literal}` is an escape. */
85894
+ const SUB_VARIABLE = /\$\{([^!}][^}]*)\}/g;
85895
+ /**
85896
+ * Which names (parameters, but also resources and pseudo parameters — the
85897
+ * caller intersects with the divergent set) the RAW `Properties` of a resource
85898
+ * depend on, plus its resource-level `Condition`.
85899
+ *
85900
+ * Generic by design: instead of modelling each intrinsic's argument grammar it
85901
+ * recurses into EVERY value, so a `Ref` nested in an `Fn::Join` / `Fn::Select`
85902
+ * / `Fn::Split` operand, an `Fn::FindInMap` key, or an `Fn::Sub` variable-map
85903
+ * value is found without being named. Only the three forms that name something
85904
+ * by BARE STRING need their own arm: `Ref`, `Fn::Sub`'s template text, and a
85905
+ * condition name (`Fn::If`'s first argument, and `Condition` inside the
85906
+ * `Conditions` section), which is followed transitively through
85907
+ * `template.Conditions`.
85908
+ *
85909
+ * May throw `RangeError` on a bag deeper than the call stack; the caller wraps
85910
+ * it fail-closed, like the discard walk beside it.
85911
+ */
85912
+ function collectParameterDependencies(properties, resourceCondition, template) {
85913
+ const names = /* @__PURE__ */ new Set();
85914
+ const attributeTargets = /* @__PURE__ */ new Set();
85915
+ const visitedConditions = /* @__PURE__ */ new Set();
85916
+ let unclassifiable = false;
85917
+ const conditions = template.Conditions !== null && typeof template.Conditions === "object" ? template.Conditions : {};
85918
+ const followCondition = (name) => {
85919
+ if (typeof name !== "string") {
85920
+ unclassifiable = true;
85921
+ return;
85922
+ }
85923
+ if (visitedConditions.has(name)) return;
85924
+ visitedConditions.add(name);
85925
+ if (!Object.hasOwn(conditions, name)) {
85926
+ unclassifiable = true;
85927
+ return;
85928
+ }
85929
+ walk(conditions[name], true);
85930
+ };
85931
+ const walk = (node, inConditions) => {
85932
+ if (node === null || typeof node !== "object") return;
85933
+ if (Array.isArray(node)) {
85934
+ for (const element of node) walk(element, inConditions);
85935
+ return;
85936
+ }
85937
+ for (const [key, value] of Object.entries(node)) {
85938
+ if (key === "Ref") {
85939
+ if (typeof value === "string") names.add(value);
85940
+ else unclassifiable = true;
85941
+ continue;
85942
+ }
85943
+ if (key === "Fn::Sub") {
85944
+ const text = Array.isArray(value) ? value[0] : value;
85945
+ if (typeof text !== "string" || Array.isArray(value) && value.length !== 2) unclassifiable = true;
85946
+ else for (const match of text.matchAll(SUB_VARIABLE)) {
85947
+ const variable = (match[1] ?? "").trim();
85948
+ names.add(variable);
85949
+ const dot = variable.indexOf(".");
85950
+ if (dot > 0) {
85951
+ names.add(variable.slice(0, dot));
85952
+ attributeTargets.add(variable.slice(0, dot));
85953
+ }
85954
+ }
85955
+ if (Array.isArray(value)) walk(value[1], inConditions);
85956
+ continue;
85957
+ }
85958
+ if (key === "Fn::GetAtt") {
85959
+ const target = Array.isArray(value) ? value[0] : value;
85960
+ if (typeof target === "string") {
85961
+ const dot = target.indexOf(".");
85962
+ const logicalId = Array.isArray(value) || dot <= 0 ? target : target.slice(0, dot);
85963
+ names.add(logicalId);
85964
+ attributeTargets.add(logicalId);
85965
+ } else unclassifiable = true;
85966
+ walk(value, inConditions);
85967
+ continue;
85968
+ }
85969
+ if (key === "Fn::If") {
85970
+ if (Array.isArray(value) && value.length === 3) {
85971
+ followCondition(value[0]);
85972
+ walk(value[1], inConditions);
85973
+ walk(value[2], inConditions);
85974
+ } else unclassifiable = true;
85975
+ continue;
85976
+ }
85977
+ if (key === "Condition" && inConditions) {
85978
+ followCondition(value);
85979
+ continue;
85980
+ }
85981
+ if (key.startsWith("Fn::") && !KNOWN_INTRINSICS.has(key)) unclassifiable = true;
85982
+ walk(value, inConditions);
85983
+ }
85984
+ };
85985
+ walk(properties, false);
85986
+ if (resourceCondition !== void 0) followCondition(resourceCondition);
85987
+ return {
85988
+ names,
85989
+ attributeTargets,
85990
+ unclassifiable
85991
+ };
85992
+ }
85993
+ /**
85994
+ * The divergent parameters that ANYTHING in the template names. A declared
85995
+ * parameter nothing names cannot have shaped a resource — and CDK's
85996
+ * `BootstrapVersion` is exactly that on every default-synthesized stack: SSM
85997
+ * typed, never bound by the resolver because nothing references it, hence
85998
+ * always "divergent". Left in, it would arm the unclassifiable-shape refusal on
85999
+ * stacks whose every real parameter is proven.
86000
+ *
86001
+ * "Names" is the UNION of this module's walk and `TemplateParser`'s reference
86002
+ * scan — the same scan the resolver uses to decide an SSM-typed parameter is
86003
+ * unreferenced — so the two cannot disagree about `BootstrapVersion`. An
86004
+ * unclassifiable shape does NOT keep the whole set here: CloudFormation names a
86005
+ * parameter only through `Ref`, `Fn::Sub` text or a condition, and the generic
86006
+ * recursion reads those inside any intrinsic, known or not. A walk that THROWS
86007
+ * keeps the whole set (fail closed).
86008
+ */
86009
+ function reachableDivergentParameters(template, divergent, rawBags) {
86010
+ if (divergent.size === 0) return /* @__PURE__ */ new Set();
86011
+ try {
86012
+ const roots = [
86013
+ rawBags,
86014
+ template.Resources,
86015
+ template.Outputs,
86016
+ template.Conditions
86017
+ ];
86018
+ const named = new Set(collectParameterDependencies(roots, void 0, template).names);
86019
+ const parser = new TemplateParser();
86020
+ for (const root of roots) {
86021
+ if (root === null || typeof root !== "object") continue;
86022
+ for (const name of parser.extractReferences(root)) named.add(name);
86023
+ }
86024
+ return new Set([...divergent].filter((name) => named.has(name)));
86025
+ } catch {
86026
+ return new Set(divergent);
86027
+ }
86028
+ }
86029
+ /** Whether any DECLARED parameter is named by anything in the template. */
86030
+ function namesAnyDeclaredParameter(template) {
86031
+ const declared = template.Parameters;
86032
+ if (declared === null || typeof declared !== "object") return false;
86033
+ const names = new Set(Object.keys(declared));
86034
+ return names.size > 0 && reachableDivergentParameters(template, names, []).size > 0;
86035
+ }
86036
+ /**
86037
+ * The per-stack ARM 4 verdict. Each resource is judged on its TEMPLATE
86038
+ * definition (`Properties` + `Condition`) AND on the raw bag the state record
86039
+ * carries, unioned: a record preserved by a selective merge holds a previous
86040
+ * run's RESOLVED literal (`Value: 'CHANGEME'`), which names no parameter, so
86041
+ * judging it on the state bag alone would leave nothing to carry to the
86042
+ * freshly imported resource that reads its attribute. Template resources that
86043
+ * are not in state at all are judged too, for the same reason.
86044
+ *
86045
+ * Seeds are direct dependents and unclassifiable bags (a bag that cannot be
86046
+ * read cannot be vouched for, and neither can what reads its attributes); the
86047
+ * refused set is then closed over ATTRIBUTE reads, to a fixpoint.
86048
+ */
86049
+ function computeParameterTaint(template, stateBags, divergent) {
86050
+ const refused = /* @__PURE__ */ new Set();
86051
+ const unclassifiable = /* @__PURE__ */ new Set();
86052
+ const parametersHit = /* @__PURE__ */ new Set();
86053
+ if (divergent.size === 0) return {
86054
+ refused,
86055
+ unclassifiable,
86056
+ parametersHit
86057
+ };
86058
+ const templateResources = template.Resources !== null && typeof template.Resources === "object" ? template.Resources : {};
86059
+ const readers = /* @__PURE__ */ new Map();
86060
+ for (const logicalId of /* @__PURE__ */ new Set([...Object.keys(templateResources), ...stateBags.keys()])) {
86061
+ const definition = Object.hasOwn(templateResources, logicalId) ? templateResources[logicalId] : void 0;
86062
+ const record = definition !== null && typeof definition === "object" ? definition : {};
86063
+ try {
86064
+ const dependencies = collectParameterDependencies([record["Properties"], stateBags.get(logicalId)], record["Condition"], template);
86065
+ readers.set(logicalId, new Set(dependencies.attributeTargets));
86066
+ const hits = [...dependencies.names].filter((name) => divergent.has(name));
86067
+ for (const name of hits) parametersHit.add(name);
86068
+ if (hits.length > 0) refused.add(logicalId);
86069
+ else if (dependencies.unclassifiable) {
86070
+ refused.add(logicalId);
86071
+ unclassifiable.add(logicalId);
86072
+ }
86073
+ } catch {
86074
+ refused.add(logicalId);
86075
+ unclassifiable.add(logicalId);
86076
+ }
86077
+ }
86078
+ for (let grew = refused.size > 0; grew;) {
86079
+ grew = false;
86080
+ for (const [logicalId, targets] of readers) {
86081
+ if (refused.has(logicalId)) continue;
86082
+ if (![...targets].some((target) => refused.has(target))) continue;
86083
+ refused.add(logicalId);
86084
+ grew = true;
86085
+ }
86086
+ }
86087
+ return {
86088
+ refused,
86089
+ unclassifiable,
86090
+ parametersHit
86091
+ };
86092
+ }
86093
+
84984
86094
  //#endregion
84985
86095
  //#region src/cli/commands/import.ts
86096
+ /**
86097
+ * What {@link resolveImportedProperties} refused, as ONE value (issue #3462).
86098
+ *
86099
+ * It is still the set of logical ids whose observed baseline must not be
86100
+ * captured — every existing reader calls `.has()` — and it additionally carries
86101
+ * the two facts `captureObservedForImportedResources` needs to write, keep or
86102
+ * clear `ResourceState.observedBaselineRefusalReason`. They travel ON the set
86103
+ * rather than beside it, and the capture's parameter is THIS type, so a call
86104
+ * site that copies the ids into a plain `Set` — dropping both facts, and with
86105
+ * them the reason every deploy-side guard keys on — does not compile.
86106
+ */
86107
+ var ObservedBaselineRefusals = class extends Set {
86108
+ /**
86109
+ * The subset ARM 4 named — directly, through an attribute read, or as an
86110
+ * unclassifiable bag — whether or not another arm refused it too.
86111
+ */
86112
+ unverifiableParameter = /* @__PURE__ */ new Set();
86113
+ /**
86114
+ * Whether this run HAD deployed parameter values to compare against. Only
86115
+ * then does "ARM 4 did not name it" mean the placeholder was PROVEN; without
86116
+ * a source it means nothing was judged at all.
86117
+ */
86118
+ hadDeployedParameterSource = false;
86119
+ };
84986
86120
  async function importCommand(stackArg, options) {
84987
86121
  const logger = getLogger();
84988
86122
  if (options.verbose) {
@@ -85183,7 +86317,8 @@ async function importCommand(stackArg, options) {
85183
86317
  }
85184
86318
  }
85185
86319
  const stackState = buildStackState(stackInfo.stackName, targetRegion, rows, templateParser, stateTemplate, existingState, selectiveMode);
85186
- await captureObservedForImportedResources(stackState, providerRegistry, logger, await withSharedDrainBudget(() => resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger)), rebuiltLogicalIdsFrom(rows, stateTemplate));
86320
+ const deployedParameters = !namesAnyDeclaredParameter(stateTemplate) ? void 0 : migrationCfnStackName !== void 0 ? await readDeployedParameters(migrationCfnStackName, awsClients.cloudFormation, logger, displaySafe(migrationCfnStackName)) : await readDeployedParameters(stackInfo.stackName, awsClients.cloudFormation, logger, displaySafe(stackInfo.stackName), { absentStackIsNoSource: true });
86321
+ await captureObservedForImportedResources(stackState, providerRegistry, logger, await withSharedDrainBudget(() => resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger, deployedParameters)), rebuiltLogicalIdsFrom(rows, stateTemplate));
85187
86322
  const saveOptions = {};
85188
86323
  if (existingEtag) saveOptions.expectedEtag = existingEtag;
85189
86324
  if (migrationPending) saveOptions.migrateLegacy = true;
@@ -85203,7 +86338,8 @@ async function importCommand(stackArg, options) {
85203
86338
  lockOwner: owner,
85204
86339
  accountId: accountIdForNestedSynth,
85205
86340
  logger,
85206
- assetRedirect
86341
+ assetRedirect,
86342
+ cfnClient: awsClients.cloudFormation
85207
86343
  });
85208
86344
  if (migrationCfnStackName) {
85209
86345
  const orphaned = resources.length - importedRows.length;
@@ -85540,6 +86676,8 @@ function rebuiltLogicalIdsFrom(rows, template) {
85540
86676
  * import flow never derives outputs (they're computed at deploy time from
85541
86677
  * each resource's attributes), so even an auto-mode rebuild has no reason
85542
86678
  * to wipe them.
86679
+ *
86680
+ * Exported for unit testing — internal to the command flow otherwise.
85543
86681
  */
85544
86682
  function buildStackState(stackName, region, rows, templateParser, template, existingState, selectiveMode) {
85545
86683
  const resources = selectiveMode && existingState ? { ...existingState.resources } : {};
@@ -85558,7 +86696,11 @@ function buildStackState(stackName, region, rows, templateParser, template, exis
85558
86696
  properties: tmplResource.Properties ?? {},
85559
86697
  attributes: rowAttributes ?? priorAttributes ?? {},
85560
86698
  dependencies: deps,
85561
- provisionedBy: "sdk"
86699
+ provisionedBy: "sdk",
86700
+ ...prior && prior.physicalId === row.physicalId && hasUnverifiableParameterRefusal(prior) && {
86701
+ observedBaselineRefused: true,
86702
+ observedBaselineRefusalReason: "unverifiable-parameter"
86703
+ }
85562
86704
  };
85563
86705
  }
85564
86706
  return {
@@ -85651,7 +86793,7 @@ function defaultOnlyParameterTemplate(template) {
85651
86793
  * the clear.
85652
86794
  *
85653
86795
  * The predicate is deliberately CONSERVATIVE — it over-refuses — and that is a
85654
- * decision a review round paid for. Three arms:
86796
+ * decision a review round paid for. Four arms:
85655
86797
  *
85656
86798
  * - **Resolution THREW** — refuse, unconditionally. A throw means the
85657
86799
  * resolver could not finish, so nothing about the persisted bag is
@@ -85692,6 +86834,15 @@ function defaultOnlyParameterTemplate(template) {
85692
86834
  * opener text and no intrinsic anywhere, because every way a discarded
85693
86835
  * subtree could have SOURCED a reference is an intrinsic.
85694
86836
  *
86837
+ * - **An UNVERIFIABLE INPUT** (ARM 4, issue
86838
+ * [#2854](https://github.com/go-to-k/cdkd/issues/2854)). The raw bag
86839
+ * depends on a template parameter whose DEPLOYED value — read from the
86840
+ * backing CloudFormation stack's `DescribeStacks`, root and every nested
86841
+ * child alike — is not provably the `Default` this import bound. Runs only
86842
+ * when `deployedParameters` is supplied; the comparison, the dependence
86843
+ * walk and the comparison-only rule for the deployed values live in
86844
+ * `import-deployed-parameters.ts`.
86845
+ *
85695
86846
  * The cost of a false refusal is one resource's drift baseline until its next
85696
86847
  * deploy; the cost of a false pass is a plaintext secret in `state.json`. So an
85697
86848
  * unmeasurable bag refuses too.
@@ -85706,8 +86857,22 @@ function defaultOnlyParameterTemplate(template) {
85706
86857
  * issue and none of them proven to be all of them:
85707
86858
  *
85708
86859
  * - a parameter binds to a placeholder `Default` while the DEPLOYED value was
85709
- * the reference, so no opener exists in any template this walk is handed --
85710
- * [#2854](https://github.com/go-to-k/cdkd/issues/2854);
86860
+ * the reference, so no opener exists in any template this walk is handed
86861
+ * ([#2854](https://github.com/go-to-k/cdkd/issues/2854)). ARM 4 below
86862
+ * refuses this when a CloudFormation stack backs the import (its
86863
+ * `DescribeStacks` parameters are the comparison); what REMAINS is an
86864
+ * import with NO CloudFormation stack, where no deployed-parameter source
86865
+ * exists, taint across the nested-stack `Outputs` boundary
86866
+ * (`import-deployed-parameters.ts` states it). The NEXT DEPLOY and a
86867
+ * source-less RE-IMPORT used to be on this list
86868
+ * ([#3462](https://github.com/go-to-k/cdkd/issues/3462)): both bind the
86869
+ * same placeholder `Default`, so both now honour
86870
+ * `ResourceState.observedBaselineRefusalReason`, which this arm's verdict
86871
+ * is recorded as. A binary older than that field still clears the marker on
86872
+ * any UPDATE. ARM 4 also WIDENS the population of
86873
+ * [#2872](https://github.com/go-to-k/cdkd/issues/2872): a record preserved
86874
+ * by a selective merge that a pre-#2854 import already gave a plaintext
86875
+ * baseline is now refused, and a refusal leaves that old baseline in place;
85711
86876
  * - `redactSecretsForState` cannot PAIR the readback against the source at a
85712
86877
  * position the source carries NO leaf for -- an observed KEY beside a paired
85713
86878
  * one -- so there is nothing to refuse from and the value scan has no needle
@@ -85732,15 +86897,17 @@ function defaultOnlyParameterTemplate(template) {
85732
86897
  * deployer, which is where the structural remedy for all of the above lives.
85733
86898
  *
85734
86899
  * The set is returned rather than recorded on the state, because it describes
85735
- * THIS run's resolution and nothing downstream of the save has any use for it.
86900
+ * THIS run's resolution; `captureObservedForImportedResources` turns it into
86901
+ * the two record fields that DO outlive the run.
85736
86902
  *
85737
86903
  * Exported for unit testing — internal to the command flow otherwise. The
85738
86904
  * masking this walk applies (issue #2803) is only provable against the REAL
85739
86905
  * resolver, since the plaintext reaches the message through the resolver's
85740
86906
  * own throw rather than through anything this function writes.
85741
86907
  */
85742
- async function resolveImportedProperties(stackState, template, region, stateBackend, logger) {
85743
- const unsafeObservedBaselineLogicalIds = /* @__PURE__ */ new Set();
86908
+ async function resolveImportedProperties(stackState, template, region, stateBackend, logger, deployedParameters) {
86909
+ const unsafeObservedBaselineLogicalIds = new ObservedBaselineRefusals();
86910
+ unsafeObservedBaselineLogicalIds.hadDeployedParameterSource = deployedParameters !== void 0;
85744
86911
  const entries = Object.entries(stackState.resources ?? {});
85745
86912
  if (entries.length === 0) return unsafeObservedBaselineLogicalIds;
85746
86913
  const resolver = new IntrinsicFunctionResolver(region);
@@ -85767,6 +86934,24 @@ async function resolveImportedProperties(stackState, template, region, stateBack
85767
86934
  logger.debug(`Template condition evaluation failed during import-time property resolution: ${err instanceof Error ? err.message : String(err)} — continuing without conditions.`);
85768
86935
  }
85769
86936
  const unboundParameterNames = Object.keys(template.Parameters ?? {}).filter((name) => isUnboundTemplateParameter(name, template, parameters));
86937
+ let divergentParameters;
86938
+ if (deployedParameters !== void 0) try {
86939
+ divergentParameters = reachableDivergentParameters(template, divergentParameterNames(template, parameters, deployedParameters), entries.map(([, resource]) => resource.properties));
86940
+ } catch {
86941
+ divergentParameters = new Set(Object.keys(template.Parameters ?? {}));
86942
+ }
86943
+ let parameterTaint;
86944
+ if (divergentParameters !== void 0 && divergentParameters.size > 0) try {
86945
+ parameterTaint = computeParameterTaint(template, new Map(entries.map(([logicalId, resource]) => [logicalId, resource.properties])), divergentParameters);
86946
+ } catch (err) {
86947
+ logger.debug(`observed-baseline parameter-dependence walk failed for '${displaySafe(stackState.stackName)}': ${err instanceof Error ? err.name : typeof err} — refusing every baseline in the stack fail-closed.`);
86948
+ const all = new Set(entries.map(([logicalId]) => logicalId));
86949
+ parameterTaint = {
86950
+ refused: all,
86951
+ unclassifiable: all,
86952
+ parametersHit: /* @__PURE__ */ new Set()
86953
+ };
86954
+ }
85770
86955
  const baseContext = {
85771
86956
  template,
85772
86957
  resources: stackState.resources,
@@ -85790,7 +86975,9 @@ async function resolveImportedProperties(stackState, template, region, stateBack
85790
86975
  threw = true;
85791
86976
  }
85792
86977
  if (recordedSecretValues.size > 0 && resource.attributes !== void 0) resource.attributes = redactSecretsForState(resource.attributes, recordedSecretValues);
85793
- if (threw) {
86978
+ const dependsOnDivergent = parameterTaint?.refused.has(logicalId) === true;
86979
+ if (dependsOnDivergent) unsafeObservedBaselineLogicalIds.unverifiableParameter.add(logicalId);
86980
+ if (threw || dependsOnDivergent) {
85794
86981
  unsafeObservedBaselineLogicalIds.add(logicalId);
85795
86982
  continue;
85796
86983
  }
@@ -85805,6 +86992,15 @@ async function resolveImportedProperties(stackState, template, region, stateBack
85805
86992
  }
85806
86993
  if (rawOpeners === void 0 || persistedOpeners === void 0 || persistedOpeners < rawOpeners || discardsNonInertSubtree) unsafeObservedBaselineLogicalIds.add(logicalId);
85807
86994
  }
86995
+ if (parameterTaint !== void 0) {
86996
+ const inState = new Set(entries.map(([logicalId]) => logicalId));
86997
+ const refusedHere = [...parameterTaint.refused].filter((id) => inState.has(id));
86998
+ const unreadable = refusedHere.filter((id) => parameterTaint.unclassifiable.has(id));
86999
+ if (refusedHere.length > 0) {
87000
+ const named = [...parameterTaint.parametersHit].sort().map((name) => displaySafe(name));
87001
+ logger.warn(`${refusedHere.length} resource(s) in '${displaySafe(stackState.stackName)}' depend, directly or through another resource's attributes, on template parameter(s) whose deployed CloudFormation value could not be proven equal to the template 'Default' 'cdkd import' binds` + (named.length > 0 ? ` (${named.join(", ")})` : "") + (unreadable.length > 0 ? ` — ${unreadable.length} of them because their properties could not be checked for such a dependence` : "") + ". Their recorded properties hold the 'Default', NOT the deployed value, and no observed drift baseline was captured for them by this import (the live value may be a secret this import cannot position a redaction for). Review 'cdkd diff' before the next 'cdkd deploy'.");
87002
+ }
87003
+ }
85808
87004
  return unsafeObservedBaselineLogicalIds;
85809
87005
  }
85810
87006
  /** The opener every dynamic reference starts with, secret-bearing or not. */
@@ -86161,15 +87357,20 @@ async function captureObservedForImportedResources(stackState, providerRegistry,
86161
87357
  if (entries.length === 0) return;
86162
87358
  await Promise.all(entries.map(async ([logicalId, resource]) => {
86163
87359
  if (resource.observedBaselineRefused === true && !rebuiltLogicalIds.has(logicalId)) {
86164
- logger.debug(`observedProperties capture SKIPPED for preserved ${logicalId} (${resource.resourceType}): a previous 'cdkd import' run refused this record's baseline and this run did not re-import it, so its recorded properties are still the ones that refusal distrusted. Deploy a change to this resource to restore a baseline.`);
87360
+ logger.debug(`observedProperties capture SKIPPED for preserved ${logicalId} (${resource.resourceType}): a previous 'cdkd import' run refused this record's baseline and this run did not re-import it, so its recorded properties are still the ones that refusal distrusted. Re-import it, or deploy a change to it, to restore a baseline — unless its refusal is an unverifiable-parameter one, which only a replacement or a proving re-import discharges.`);
86165
87361
  return;
86166
87362
  }
86167
- if (unsafeObservedBaselineLogicalIds.has(logicalId)) {
87363
+ const refusals = unsafeObservedBaselineLogicalIds;
87364
+ const parameterRefusalStands = refusals.unverifiableParameter.has(logicalId) || hasUnverifiableParameterRefusal(resource) && !refusals.hadDeployedParameterSource;
87365
+ if (unsafeObservedBaselineLogicalIds.has(logicalId) || parameterRefusalStands) {
86168
87366
  resource.observedBaselineRefused = true;
86169
- logger.debug(`observedProperties capture SKIPPED for imported ${logicalId} (${resource.resourceType}): the recorded properties no longer spell the template's dynamic reference, so they cannot position a redaction — capturing an AWS readback against them could persist a resolved secret in plaintext. Drift will compare against the recorded properties for this resource until the next successful deploy.`);
87367
+ if (parameterRefusalStands) resource.observedBaselineRefusalReason = "unverifiable-parameter";
87368
+ else delete resource.observedBaselineRefusalReason;
87369
+ logger.debug(`observedProperties capture SKIPPED for imported ${logicalId} (${resource.resourceType}): the recorded properties cannot be shown to spell every dynamic reference the deployed resource was built from, so they cannot position a redaction — capturing an AWS readback against them could persist a resolved secret in plaintext. Drift will compare against the recorded properties for this resource until ${parameterRefusalStands ? `it is REPLACED, or re-imported while a CloudFormation stack can prove its parameters were deployed at their 'Default' — an in-place deploy binds the same 'Default' and cannot restore the baseline` : `the next successful deploy`}.`);
86170
87370
  return;
86171
87371
  }
86172
87372
  delete resource.observedBaselineRefused;
87373
+ delete resource.observedBaselineRefusalReason;
86173
87374
  try {
86174
87375
  const provider = providerRegistry.getProviderFor({
86175
87376
  resourceType: resource.resourceType,
@@ -86349,7 +87550,8 @@ async function importNestedStackChildrenRecursive(args) {
86349
87550
  childStackState.parentStack = parentStackName;
86350
87551
  childStackState.parentLogicalId = childLogicalId;
86351
87552
  childStackState.parentRegion = parentRegion;
86352
- await captureObservedForImportedResources(childStackState, providerRegistry, logger, await withSharedDrainBudget(() => resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger)), rebuiltLogicalIdsFrom(rows, childTemplate));
87553
+ const childDeployedParameters = namesAnyDeclaredParameter(childStateTemplate) ? await readDeployedParameters(childTreeNode.stackName, args.cfnClient, logger, displaySafe(childStackName)) : void 0;
87554
+ await captureObservedForImportedResources(childStackState, providerRegistry, logger, await withSharedDrainBudget(() => resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger, childDeployedParameters)), rebuiltLogicalIdsFrom(rows, childTemplate));
86353
87555
  await stateBackend.saveState(childStackName, childRegion, childStackState);
86354
87556
  logger.info(`✓ Nested stack state written: ${childStackName} (${childRegion}) — ${rows.filter((r) => r.outcome === "imported").length} resource(s) imported.`);
86355
87557
  if (childTreeNode.nested.size > 0) await importNestedStackChildrenRecursive({
@@ -86365,7 +87567,8 @@ async function importNestedStackChildrenRecursive(args) {
86365
87567
  lockOwner,
86366
87568
  accountId,
86367
87569
  logger,
86368
- assetRedirect
87570
+ assetRedirect,
87571
+ cfnClient: args.cfnClient
86369
87572
  });
86370
87573
  } finally {
86371
87574
  await lockManager.releaseLock(childStackName, childRegion).catch((err) => {
@@ -96038,4 +97241,4 @@ function buildProgram() {
96038
97241
 
96039
97242
  //#endregion
96040
97243
  export { buildProgram };
96041
- //# sourceMappingURL=program-Dh1J573K.js.map
97244
+ //# sourceMappingURL=program-DcMcqr70.js.map