@go-to-k/cdkd 0.290.15 → 0.290.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
- import { $ as secretBearing, $n as BOOTSTRAP_MARKER_PREFIX, $r as getAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as markNonRetryable, An as buildForceUnlockCommand, Ar as resolveAutoAssetStorage, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as importableOutputs, Br as MIGRATE_TMP_PREFIX, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Cn as recordMaskOnlyValue, Cr as AssetManifestLoader, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isRetryableTransientError, Dn as LockManager, Dr as getDefaultStateBucketName, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isMarkedNonRetryable, En as scrubResourceRecord, Er as synthesisStatusMessage, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as resolveUseCdkBootstrapAssets, Ft as resolveExplicitPhysicalId, G as gray, Gn as AssetPublisher, Gr as PARTITION_TABLE, Gt as requireConfigObject, H as formatResourceLine, Hn as orphansAfterRollback, Hr as uploadCfnTemplate, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as stateBucketExistenceConfirmed, It as assertRegionMatch, J as yellow, Jn as buildAssetRedirectMap, Jr as AssemblyReader, Jt as producerRegionsFromState, K as green, Kn as stringifyValue, Kr as canonicalizeRegion, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as warnDeprecatedNoPrefixCliFlag, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mi as retryClassificationText, Mn as forceQuitRecoveryClause, Mr as resolveSkipPrefix, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveStateBucketWithDefault, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as isThrottlingError, On as S3StateBackend, Or as getLegacyStateBucketName, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as resolveStateBucketWithDefaultAndSource, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as AssetModeResolver, Qr as AwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as hasReadableExportSet, Rr as CFN_TEMPLATE_BODY_LIMIT, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Sn as maskSecretsInText, Sr as stripControlChars, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as withErrorHandling, Tn as redactSecretsForState, Tr as Synthesizer, Tt as cfnRefValueFromPhysicalId, U as bold, Un as orphansCarriedFrom, Ur as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Ut as replayWarn, V as toFiniteNumber, Vn as isReadableBag, Vr as findLargeInlineResources, Vt as configStringRefusal, W as cyan, Wn as shouldRetainResource, Wr as expectedOwnerParam, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as loadPublishableAssetManifest, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as createAssetRedirectResolver, Yr as processStackMessages, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as rewriteTemplateAssetReferences, Zr as resolveBucketRegion, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as ResourceUpdateNotSupportedError, _n as errorCauseChain, _r as partitionSensitiveEnv, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as CrossAccountSecretRefusalError, an as findSilentDropProperties, ar as readBootstrapMarkerBody, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as runDockerStreaming, bt as deleteSkipReason, c as replayFailedOperations, ci as DynamicReferenceRegionAmbiguousError, cn as DagBuilder, cr as buildDenyExternalAccessPolicy, ct as createMaskedRetryLogger, d as updatePartialReason, di as LocalStartServiceError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerCapturedOutput, dt as beginCommandInterruptScope, ei as resetAwsClients, en as s3BucketWebsiteUrl, er as assertAssetBucketRegion, et as secretBearingStateKeyWarning, f as withResourceDeadline, fn as STATE_SOURCED_READBACK_RULES, fr as describeDockerExecFailure, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as ResourceTimeoutError, gn as dynamicReferenceTokens, gr as getDockerCmd, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ProvisioningError, hn as createSecretMasker, hr as formatDockerLoginError, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as parseBootstrapMarker, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, ji as markRedactedCause, jn as buildLockContentionMessage, jr as resolveCaptureObservedState, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as isTransientServerError, kn as rebuildClientForBucketRegion, kr as resolveApp, kt as isUnboundTemplateParameter, l as replayRollback, li as IntrinsicResolutionRefusalError, ln as TemplateParser, lr as describeAwsFailure, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as PartialFailureError, mn as carriesSecretMask, mr as dockerSpawnEnvWithSensitive, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as getBootstrapMarkerKey, nt as getCurrentResourceSecrets, o as planFailedOps, on as describeTypeWithThrottleRetry, or as validateAssetBucketName, ot as ProviderRegistry, p as maskingRetryLogger, pi as NestedStackChildDirectDestroyError, pn as TEMPLATE_SOURCED_RULES, pr as describeDockerFailure, pt as interruptWatchListenerCount, q as red, qn as WorkGraph, qr as derivePartitionAndUrlSuffix, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CdkdError, rn as INTRINSIC_KEYS, rr as isCrossRegionRedirect, rt as IAMRoleProvider, s as planRollback, si as DeployCancelledError, sn as withRetry, sr as validateContainerRepoName, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as setAwsClients, tn as applyRoleArnIfSet, tr as ensureAssetStorage, tt as secretSafeKeyDisplay, u as updatePartialMessage, ui as LocalInvokeBuildError$1, un as STATE_SOURCED_BASELINE_RULES, ur as buildDockerImage, ut as maskerOrIdentity, v as keptWholeReasonText, vi as StackHasActiveImportsError, vn as identityKeyFor, vr as redactDockerArgvValues, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as normalizeAwsError, wn as recoverMaskedOutput, wr as getDockerImageBySourceHash, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as escapeRegExp, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yi as StackTerminationProtectionError, yn as isSingleDynamicReferenceToken, yr as runDockerForeground, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputKeys, zr as CFN_TEMPLATE_URL_LIMIT, zt as configBooleanRefusal } from "./deploy-engine-C2qLzc-5.js";
1
+ import { $ as secretBearing, $n as BOOTSTRAP_MARKER_PREFIX, $r as getAwsClients, $t as s3BucketRegionalDomainName, A as extractDeploymentEventError, Ai as markNonRetryable, An as buildForceUnlockCommand, Ar as resolveAutoAssetStorage, At as parameterTypeMayLoseSecretIdentity, B as toCfnInteger, Bn as importableOutputs, Br as MIGRATE_TMP_PREFIX, Bt as configIntegerRefusal, C as buildFinalSnapshotIdentifier, Cn as recordMaskOnlyValue, Cr as AssetManifestLoader, Ct as IntrinsicFunctionResolver, D as refusesFinalSnapshot, Di as isRetryableTransientError, Dn as LockManager, Dr as getDefaultStateBucketName, Dt as getAccountInfo, E as isFinalSnapshotError, Ei as isMarkedNonRetryable, En as scrubResourceRecord, Er as synthesisStatusMessage, Et as coerceParameterTypedValue, F as isStatefulRecreateTargetSync, Fn as CUSTOM_RESOURCE_RESPONSE_PREFIX, Fr as resolveUseCdkBootstrapAssets, Ft as resolveExplicitPhysicalId, G as gray, Gn as AssetPublisher, Gr as PARTITION_TABLE, Gt as requireConfigObject, H as formatResourceLine, Hn as orphansAfterRollback, Hr as uploadCfnTemplate, Ht as readConfigString, I as renderStatefulReason, In as DEFAULT_STATE_PREFIX, Ir as stateBucketExistenceConfirmed, It as assertRegionMatch, J as yellow, Jn as buildAssetRedirectMap, Jr as AssemblyReader, Jt as producerRegionsFromState, K as green, Kn as stringifyValue, Kr as canonicalizeRegion, Kt as requireConfigString, L as WARM_THROUGHPUT_MEMBERS, Ln as exportNamesCarriedFrom, Lr as warnDeprecatedNoPrefixCliFlag, Lt as coerceCfnBoolean, M as planOrphanAdoption, Mi as retryClassificationText, Mn as forceQuitRecoveryClause, Mr as resolveSkipPrefix, Mt as isSettledInstanceState, N as MULTI_REGION_RECREATE_BLOCKED_TYPES, Nn as formatLockExpiry, Nr as resolveStateBucketWithDefault, Nt as WAFv2WebACLProvider, O as unsupportedFinalSnapshotError, Oi as isThrottlingError, On as S3StateBackend, Or as getLegacyStateBucketName, Ot as isImpossibleEmptyStoredAttribute, P as isStatefulRecreateTargetForReplace, Pn as shellQuote, Pr as resolveStateBucketWithDefaultAndSource, Pt as normalizeAwsTagsToCfn, Q as isExportAliasCollision, Qn as AssetModeResolver, Qr as AwsClients, Qt as s3BucketDualStackDomainName, R as coerceWarmThroughput$1, Rn as hasReadableExportSet, Rr as CFN_TEMPLATE_BODY_LIMIT, Rt as coerceCfnInteger, S as PRE_DELETE_SNAPSHOT_TYPES, Sn as maskSecretsInText, Sr as stripControlChars, St as isTerminationProtectionPropagationError, T as createPreDeleteFinalSnapshot, Ti as withErrorHandling, Tn as redactSecretsForState, Tr as Synthesizer, Tt as cfnRefValueFromPhysicalId, U as bold, Un as orphansCarriedFrom, Ur as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, Ut as replayWarn, V as toFiniteNumber, Vn as isReadableBag, Vr as findLargeInlineResources, Vt as configStringRefusal, W as cyan, Wn as shouldRetainResource, Wr as expectedOwnerParam, Wt as requireConfigArray, X as collectPublishedOutputNames, Xn as loadPublishableAssetManifest, Xt as s3BucketArn, Y as collectDeclaredOutputNames, Yn as createAssetRedirectResolver, Yr as processStackMessages, Yt as withSharedDrainBudget, Z as exportAliasCollisionScrubWarning, Zn as rewriteTemplateAssetReferences, Zr as resolveBucketRegion, Zt as s3BucketDomainName, _ as isSecretBearingReferenceString, _i as ResourceUpdateNotSupportedError, _n as errorCauseChain, _r as partitionSensitiveEnv, _t as slowCcOperationTimeoutMs, a as DeploymentEventsStore, ai as CrossAccountSecretRefusalError, an as findSilentDropProperties, ar as readBootstrapMarkerBody, at as clearOnUpdateRemoval, b as bindingSkippedOutputs, bn as markSameGenerationBag, br as runDockerStreaming, bt as deleteSkipReason, c as replayFailedOperations, ci as DynamicReferenceRegionAmbiguousError, cn as DagBuilder, cr as buildDenyExternalAccessPolicy, ct as createMaskedRetryLogger, d as updatePartialReason, di as LocalStartServiceError, dn as STATE_SOURCED_CROSS_GENERATION_RULES, dr as describeDockerCapturedOutput, dt as beginCommandInterruptScope, ei as resetAwsClients, en as s3BucketWebsiteUrl, er as assertAssetBucketRegion, et as secretBearingStateKeyWarning, f as withResourceDeadline, fn as STATE_SOURCED_READBACK_RULES, fr as describeDockerExecFailure, ft as endCommandInterruptScope, g as bagHoldsSecretExpression, gi as ResourceTimeoutError, gn as dynamicReferenceTokens, gr as getDockerCmd, gt as CloudControlProvider, h as computeImplicitDeleteEdges, hi as ProvisioningError, hn as createSecretMasker, hr as formatDockerLoginError, ht as startInterruptWatch, i as DeploymentEventsReader, in as findActionableSilentDrops, ir as parseBootstrapMarker, it as collectInlinePolicyNamesManagedBySiblings, j as makeSiblingClaimReader, ji as markRedactedCause, jn as buildLockContentionMessage, jr as resolveCaptureObservedState, jt as refStateLookupFromResource, k as makeCanonicalizePropertiesFn, ki as isTransientServerError, kn as rebuildClientForBucketRegion, kr as resolveApp, kt as isUnboundTemplateParameter, l as replayRollback, li as IntrinsicResolutionRefusalError, ln as TemplateParser, lr as describeAwsFailure, lt as maskDeep, m as IMPLICIT_DELETE_DEPENDENCIES, mi as PartialFailureError, mn as carriesSecretMask, mr as dockerSpawnEnvWithSensitive, mt as isInterruptedWaitError, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as DiffCalculator, nr as getBootstrapMarkerKey, nt as getCurrentResourceSecrets, o as planFailedOps, on as describeTypeWithThrottleRetry, or as validateAssetBucketName, ot as ProviderRegistry, p as maskingRetryLogger, pi as NestedStackChildDirectDestroyError, pn as TEMPLATE_SOURCED_RULES, pr as describeDockerFailure, pt as interruptWatchListenerCount, q as red, qn as WorkGraph, qr as derivePartitionAndUrlSuffix, qt as classifyReplaySecretRegion, r as DeployEngine, ri as CdkdError, rn as INTRINSIC_KEYS, rr as isCrossRegionRedirect, rt as IAMRoleProvider, s as planRollback, si as DeployCancelledError, sn as withRetry, sr as validateContainerRepoName, st as wouldReturnToSdkProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as setAwsClients, tn as applyRoleArnIfSet, tr as ensureAssetStorage, tt as secretSafeKeyDisplay, u as updatePartialMessage, ui as LocalInvokeBuildError$1, un as STATE_SOURCED_BASELINE_RULES, ur as buildDockerImage, ut as maskerOrIdentity, v as keptWholeReasonText, vi as StackHasActiveImportsError, vn as identityKeyFor, vr as redactDockerArgvValues, vt as UNSPECIFIED_SKIP_REASON, w as ccRoutedFinalSnapshotError, wi as normalizeAwsError, wn as recoverMaskedOutput, wr as getDockerImageBySourceHash, wt as carriesDynamicReference, x as ATOMIC_FINAL_SNAPSHOT_TYPES, xn as maskSecretsInError, xr as escapeRegExp, xt as disableInstanceApiTermination, y as mergeNoChangeOutputs, yi as StackTerminationProtectionError, yn as isSingleDynamicReferenceToken, yr as runDockerForeground, yt as deleteIndeterminateGuards, z as isWarmThroughputDecrease, zn as importableOutputKeys, zr as CFN_TEMPLATE_URL_LIMIT, zt as configBooleanRefusal } from "./deploy-engine-BYTwDxet.js";
2
2
  import { C as displaySafe, S as displayIdent, _ as withSkipPrefix, b as STACK_REF_MAX_CODE_POINTS, c as PATTERN_B_NAME_OPTIONS, f as explicitNamePropertyFor, h as getCurrentStackName, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as generateResourceNameWithFallback, n as getLogger, o as runStackBuffered, p as generateResourceName, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES, v as withStackName, w as truncateCodePoints, x as UNRENDERABLE } from "./logger-C17H06QA.js";
3
3
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
4
- import { t as getCdkdVersion } from "./version-BnegtHzZ.js";
5
- import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-D5-3SGmd.js";
4
+ import { t as getCdkdVersion } from "./version-CT2xso6V.js";
5
+ import { a as renderDisableCommand, i as protectedReplacementAdvice, r as UNNAMEABLE_ID_CLAUSE, t as ASGProvider } from "./asg-provider-BPBdAAGQ.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";
@@ -7079,6 +7079,46 @@ function isTruthyCfnBoolean(value) {
7079
7079
  //#endregion
7080
7080
  //#region src/provisioning/providers/s3-bucket-provider.ts
7081
7081
  /**
7082
+ * The "remove this bucket by hand" clause both partial-create cleanup arms end
7083
+ * with, rendered through the shared sanitize / shell-quote / SUPPRESS
7084
+ * (`renderDisableCommand`, the issue
7085
+ * [#2669](https://github.com/go-to-k/cdkd/issues/2669) shape; adopted here by
7086
+ * issue [#3136](https://github.com/go-to-k/cdkd/issues/3136)).
7087
+ *
7088
+ * `bucketName` is TEMPLATE-chosen — `Properties.BucketName`, or the name cdkd
7089
+ * generated for the resource — and until now both arms hand-quoted it as
7090
+ * `--bucket '<name>'`, so a `'` in the value broke out of the quoting and a
7091
+ * control byte forged lines on the operator's terminal. (A WARN, so that is
7092
+ * its whole reach — the recorder persists a thrown `error.message`, not this.)
7093
+ * When sanitizing CHANGES the name the whole command is
7094
+ * SUPPRESSED rather than shown with the sanitized value, because such a command
7095
+ * would delete a DIFFERENT bucket — the wrong-target harm the shared renderer
7096
+ * exists to prevent, and the reason emitting no command is the honest answer
7097
+ * rather than a fallback.
7098
+ *
7099
+ * ONE clause for both arms so the two cannot come to render the same value
7100
+ * differently. The prose `(${bucketName})` each arm carries BESIDE it is still
7101
+ * interpolated as-is: this clause takes only the pasteable half, and
7102
+ * display-sanitizing the PROSE of provider warnings is tracked as issue
7103
+ * [#3269](https://github.com/go-to-k/cdkd/issues/3269) (#3136 covers the
7104
+ * COMMAND half across providers, not prose).
7105
+ *
7106
+ * `maskSecrets` is REQUIRED rather than optional, and both call sites build it
7107
+ * from `context?.maskSecrets` (security review of #3136, which found these two
7108
+ * arms reaching no masker at all). It does two things: the arms now route
7109
+ * their whole warning through it, and it makes `renderDisableCommand` SUPPRESS
7110
+ * the command for a secret-bearing bucket name — which a message-level mask
7111
+ * could not catch once `shellQuote` has escaped an inner quote.
7112
+ */
7113
+ function manualBucketDeletionClause(bucketName, maskSecrets) {
7114
+ const command = renderDisableCommand({
7115
+ before: "aws s3api delete-bucket --bucket",
7116
+ identifier: bucketName,
7117
+ maskSecrets
7118
+ });
7119
+ return command ? `Manual deletion may be required before the next deploy: ${command}` : "Manual deletion may be required before the next deploy, via the console: the bucket name cannot be reproduced safely on a command line, so any command shown here would act on a different bucket.";
7120
+ }
7121
+ /**
7082
7122
  * A plain (non-array) object. `typeof x === 'object'` alone accepts arrays and
7083
7123
  * `null`; a `Transition: []` or an unresolved intrinsic pushed through as a
7084
7124
  * single transition entry makes S3 answer `MalformedXML` for the WHOLE
@@ -10440,11 +10480,13 @@ var S3BucketProvider = class S3BucketProvider {
10440
10480
  this.logger.debug(`Cleaned up partially-created S3 bucket ${logicalId} (${bucketName}) after wiring failure`);
10441
10481
  } catch (cleanupError) {
10442
10482
  this.logger.debug(`DeleteBucket cleanup failed for S3 bucket ${logicalId} (${bucketName}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
10443
- this.logger.warn(`Failed to clean up partially-created S3 bucket ${logicalId} (${bucketName}) (${cleanupError instanceof Error ? cleanupError.name : typeof cleanupError}). Re-run with --verbose for AWS's own message. Manual deletion may be required before the next deploy: aws s3api delete-bucket --bucket '${bucketName}'`);
10483
+ const maskSecrets = maskerOrIdentity(context?.maskSecrets);
10484
+ this.logger.warn(maskSecrets(`Failed to clean up partially-created S3 bucket ${logicalId} (${bucketName}) (${cleanupError instanceof Error ? cleanupError.name : typeof cleanupError}). Re-run with --verbose for AWS's own message. ` + manualBucketDeletionClause(bucketName, maskSecrets)));
10444
10485
  }
10445
10486
  else if (preflight.kind === "indeterminate") {
10446
10487
  this.logger.debug(`GetBucketLocation failed for S3 bucket ${bucketName} (${logicalId}) during create: ${preflight.reason}`);
10447
- this.logger.warn(`Not cleaning up S3 bucket ${logicalId} (${bucketName}) after a wiring failure: this deploy could not confirm whether it created the bucket (region probe failed: ${preflight.errorName}), and in us-east-1 a successful CreateBucket does not prove it. Re-run with --verbose for AWS's own message. If cdkd created it, delete it manually before the next deploy: aws s3api delete-bucket --bucket '${bucketName}'`);
10488
+ const maskSecrets = maskerOrIdentity(context?.maskSecrets);
10489
+ this.logger.warn(maskSecrets(`Not cleaning up S3 bucket ${logicalId} (${bucketName}) after a wiring failure: this deploy could not confirm whether it created the bucket (region probe failed: ${preflight.errorName}), and in us-east-1 a successful CreateBucket does not prove it. Re-run with --verbose for AWS's own message. If cdkd created it, the bucket is an orphan. ` + manualBucketDeletionClause(bucketName, maskSecrets)));
10448
10490
  }
10449
10491
  throw innerError;
10450
10492
  }
@@ -17894,10 +17936,28 @@ function mapSSESpecification(raw) {
17894
17936
  * still are not.
17895
17937
  */
17896
17938
  function readCapacityNumber(block, member) {
17897
- if (typeof block !== "object" || block === null || Array.isArray(block)) return void 0;
17939
+ if (!isPlainCapacityBlock(block)) return void 0;
17898
17940
  return coerceCfnInteger(block[member]);
17899
17941
  }
17900
17942
  /**
17943
+ * Is this a `ProvisionedThroughput` block whose MEMBERS can be read at all?
17944
+ *
17945
+ * One spelling, shared by {@link readCapacityNumber} — where a `false` answer
17946
+ * means "no usable member" — and by
17947
+ * {@link DynamoDBTableProvider.coerceIndexCapacityBlock}, where it means
17948
+ * "forward this VERBATIM and let AWS name the shape" (issue
17949
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255)). The two conclusions
17950
+ * differ; the shape test must not, or the create forwarder could rewrite a
17951
+ * block the flip's reader refuses to read (or vice versa).
17952
+ *
17953
+ * An ARRAY is excluded deliberately: `[]` indexes to `undefined` for both
17954
+ * members, so treating it as a block would DROP nothing and report nothing
17955
+ * while `CreateTable` rejects the list by name anyway.
17956
+ */
17957
+ function isPlainCapacityBlock(value) {
17958
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17959
+ }
17960
+ /**
17901
17961
  * Whether a desired `GlobalSecondaryIndexes[]` entry declares BOTH capacity
17902
17962
  * members usably — the same test the BillingMode flip's per-index block applies
17903
17963
  * before it forwards the index, expressed as a predicate for the pre-flip
@@ -17919,11 +17979,23 @@ function hasUsableDeclaredCapacity(entry) {
17919
17979
  return readCapacityNumber(declared, "ReadCapacityUnits") !== void 0 && readCapacityNumber(declared, "WriteCapacityUnits") !== void 0;
17920
17980
  }
17921
17981
  /**
17922
- * The two TABLE-level `ProvisionedThroughput` members, in the order both
17923
- * forwarders build them. Named once so {@link hasUsableTableCapacity} and
17924
- * {@link tableCapacityForSend} cannot come to disagree about the membership.
17982
+ * The two `ProvisionedThroughput` members, in the order the forwarders build
17983
+ * them. Named once so {@link hasUsableTableCapacity}, {@link tableCapacityForSend}
17984
+ * and the per-INDEX create forwarder
17985
+ * ({@link DynamoDBTableProvider.coerceIndexCapacityBlock}) cannot come to
17986
+ * disagree about the membership.
17987
+ *
17988
+ * Deliberately NOT `TABLE_CAPACITY_MEMBERS` any more (issue
17989
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255)): CloudFormation's
17990
+ * `ProvisionedThroughput` block has the same two members at the TABLE level and
17991
+ * inside every `GlobalSecondaryIndexes[]` entry, and a second list for the
17992
+ * per-index reader would be exactly the drift this constant exists to prevent.
17993
+ * What differs between the two levels is what an ABSENT member does — the table
17994
+ * level substitutes {@link DEFAULT_TABLE_CAPACITY_UNITS}, the per-index level
17995
+ * deliberately has no default ({@link readCapacityNumber}) — and that is a
17996
+ * property of each forwarder, not of the membership.
17925
17997
  */
17926
- const TABLE_CAPACITY_MEMBERS = ["ReadCapacityUnits", "WriteCapacityUnits"];
17998
+ const PROVISIONED_CAPACITY_MEMBERS = ["ReadCapacityUnits", "WriteCapacityUnits"];
17927
17999
  /**
17928
18000
  * The capacity both TABLE-level forwarders substitute for a member the
17929
18001
  * template does not declare at all — `create()`'s `CreateTable` and the
@@ -17967,7 +18039,7 @@ function hasUsableTableCapacity(value) {
17967
18039
  if (!value) return false;
17968
18040
  if (typeof value !== "object" || Array.isArray(value)) return true;
17969
18041
  const pt = value;
17970
- for (const member of TABLE_CAPACITY_MEMBERS) {
18042
+ for (const member of PROVISIONED_CAPACITY_MEMBERS) {
17971
18043
  const raw = pt[member];
17972
18044
  if (raw === void 0 || raw === null) continue;
17973
18045
  const n = coerceCfnInteger(raw);
@@ -18374,7 +18446,7 @@ function reverseMapSecondaryIndex(live, desired, bagInformative) {
18374
18446
  * predicate EVERY per-index write path calls — the `Create` / `Update` GSI
18375
18447
  * actions in `applyGsiUpdates` AND `create()`'s `CreateTable` forward, which
18376
18448
  * maps each entry through
18377
- * {@link DynamoDBTableProvider.coerceIndexWarmThroughputForCreate}. It is
18449
+ * {@link DynamoDBTableProvider.coerceIndexThroughputForCreate}. It is
18378
18450
  * TIGHTER than truthiness (at least one member resolving to a finite
18379
18451
  * number), so `WarmThroughput: {}` is neither sent nor emitted on any path.
18380
18452
  * An earlier version of this bullet carved out the TABLE-create path as an
@@ -18383,9 +18455,15 @@ function reverseMapSecondaryIndex(live, desired, bagInformative) {
18383
18455
  * template failed on a fresh create and succeeded on a later GSI add. Fixed
18384
18456
  * in PR review round 6; the carve-out is gone because the divergence is.
18385
18457
  * - `ProvisionedThroughput` / `OnDemandThroughput` keep TRUTHINESS, which is
18386
- * what their write path does: those two members ARE still forwarded verbatim
18387
- * (the create-path mapper rewrites only `WarmThroughput`), and
18388
- * `applyGsiUpdates` gates on `gsi.ProvisionedThroughput` alone. Widening
18458
+ * still what their write path does: `applyGsiUpdates` gates on
18459
+ * `gsi.ProvisionedThroughput` alone, `OnDemandThroughput` is forwarded
18460
+ * verbatim on every path, and the `ProvisionedThroughput` coercion issue
18461
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255) added to ALL FOUR
18462
+ * per-index send sites (`create()`'s mapper plus `applyGsiUpdates`'
18463
+ * adopted-repair, `Create` and same-name `Update` actions) never
18464
+ * SUPPRESSES the block — it rewrites members inside a block that is sent
18465
+ * either way, so "would cdkd send this block" is unchanged by it, even
18466
+ * where both members were dropped and an empty block goes out. Widening
18389
18467
  * `isSendableWarmThroughput` over them would be wrong twice over — it reads
18390
18468
  * members those blocks do not have (`ReadUnitsPerSecond` vs
18391
18469
  * `ReadCapacityUnits`), so every declared capacity would read as undeclared.
@@ -18577,7 +18655,7 @@ var DynamoDBTableProvider = class {
18577
18655
  };
18578
18656
  if (billingMode === "PROVISIONED") {
18579
18657
  const pt = properties["ProvisionedThroughput"];
18580
- const onUnusable = (member, raw) => this.warnUnusableTableCapacity(`AWS::DynamoDB::Table ${logicalId}`, member, raw, maskSecrets);
18658
+ const onUnusable = (member, raw) => this.warnUnusableProvisionedCapacity(`AWS::DynamoDB::Table ${logicalId}`, member, raw, maskSecrets);
18581
18659
  createParams.ProvisionedThroughput = {
18582
18660
  ReadCapacityUnits: tableCapacityForSend(pt, "ReadCapacityUnits", onUnusable),
18583
18661
  WriteCapacityUnits: tableCapacityForSend(pt, "WriteCapacityUnits", onUnusable)
@@ -18593,7 +18671,7 @@ var DynamoDBTableProvider = class {
18593
18671
  };
18594
18672
  if (properties["GlobalSecondaryIndexes"]) {
18595
18673
  const declaredGsis = properties["GlobalSecondaryIndexes"];
18596
- createParams.GlobalSecondaryIndexes = Array.isArray(declaredGsis) ? declaredGsis.map((entry) => this.coerceIndexWarmThroughputForCreate(logicalId, entry, maskSecrets)) : declaredGsis;
18674
+ createParams.GlobalSecondaryIndexes = Array.isArray(declaredGsis) ? declaredGsis.map((entry) => this.coerceIndexThroughputForCreate(logicalId, entry, maskSecrets)) : declaredGsis;
18597
18675
  }
18598
18676
  if (properties["LocalSecondaryIndexes"]) createParams.LocalSecondaryIndexes = properties["LocalSecondaryIndexes"];
18599
18677
  const sse = mapSSESpecification(properties["SSESpecification"]);
@@ -18711,7 +18789,7 @@ var DynamoDBTableProvider = class {
18711
18789
  if (tableClassChanged) updateInput.TableClass = normalizeTableClass(properties["TableClass"]);
18712
18790
  if (billingOrThroughputChanged && billingMode !== "PAY_PER_REQUEST" && properties["ProvisionedThroughput"]) {
18713
18791
  const pt = properties["ProvisionedThroughput"];
18714
- const onUnusable = (member, raw) => this.warnUnusableTableCapacity(`AWS::DynamoDB::Table ${logicalId}`, member, raw, maskSecrets);
18792
+ const onUnusable = (member, raw) => this.warnUnusableProvisionedCapacity(`AWS::DynamoDB::Table ${logicalId}`, member, raw, maskSecrets);
18715
18793
  updateInput.ProvisionedThroughput = {
18716
18794
  ReadCapacityUnits: tableCapacityForSend(pt, "ReadCapacityUnits", onUnusable),
18717
18795
  WriteCapacityUnits: tableCapacityForSend(pt, "WriteCapacityUnits", onUnusable)
@@ -19403,7 +19481,7 @@ var DynamoDBTableProvider = class {
19403
19481
  const adopted = { IndexName: name };
19404
19482
  let adoptedHasMember = false;
19405
19483
  if (liveCapacityComparable && gsi.ProvisionedThroughput && !handledByBillingFlip.has(name) && !liveCapacityAlreadyMatches(live.ProvisionedThroughput, gsi.ProvisionedThroughput) && !this.skipZeroCapacityIndexUpdate(name, physicalId, gsi.ProvisionedThroughput, maskSecrets)) {
19406
- adopted.ProvisionedThroughput = gsi.ProvisionedThroughput;
19484
+ adopted.ProvisionedThroughput = this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets);
19407
19485
  adoptedHasMember = true;
19408
19486
  }
19409
19487
  const adoptedWarm = this.warmThroughputOpFor(name, physicalId, gsi, void 0, live, maskSecrets);
@@ -19415,13 +19493,13 @@ var DynamoDBTableProvider = class {
19415
19493
  warn(`GSI ${maskSecrets(name)} was adopted from AWS rather than created, so cdkd could not verify its KeySchema / Projection match the template. Run \`cdkd drift ${physicalId}\` to confirm, and \`cdkd drift --accept\` if AWS is the source of truth.`);
19416
19494
  continue;
19417
19495
  }
19418
- this.warnRefusedWarmThroughput(`GSI ${maskSecrets(name)} on DynamoDB table ${physicalId}`, gsi.WarmThroughput, maskSecrets);
19419
- const createWarm = this.coerceWarmThroughputForSend(`GSI ${maskSecrets(name)} on DynamoDB table ${physicalId}`, gsi.WarmThroughput, maskSecrets);
19496
+ this.warnRefusedWarmThroughput(this.indexScopeAt(name, physicalId, maskSecrets), gsi.WarmThroughput, maskSecrets);
19497
+ const createWarm = this.coerceWarmThroughputForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.WarmThroughput, maskSecrets);
19420
19498
  ops.push({ Create: {
19421
19499
  IndexName: name,
19422
19500
  KeySchema: gsi.KeySchema,
19423
19501
  Projection: gsi.Projection,
19424
- ...gsi.ProvisionedThroughput ? { ProvisionedThroughput: gsi.ProvisionedThroughput } : {},
19502
+ ...gsi.ProvisionedThroughput ? { ProvisionedThroughput: this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets) } : {},
19425
19503
  ...gsi.OnDemandThroughput ? { OnDemandThroughput: gsi.OnDemandThroughput } : {},
19426
19504
  ...createWarm ? { WarmThroughput: createWarm } : {}
19427
19505
  } });
@@ -19433,7 +19511,7 @@ var DynamoDBTableProvider = class {
19433
19511
  if (gsi.ProvisionedThroughput && !handledByBillingFlip.has(name) && JSON.stringify(before.ProvisionedThroughput) !== JSON.stringify(gsi.ProvisionedThroughput)) {
19434
19512
  if (liveCapacityComparable && liveCapacityAlreadyMatches(liveIndexByName?.get(name)?.ProvisionedThroughput, gsi.ProvisionedThroughput)) this.logger.debug(`GSI ${name} on DynamoDB table ${physicalId} already carries the requested capacity in AWS; skipping its throughput Update`);
19435
19513
  else if (!this.skipZeroCapacityIndexUpdate(name, physicalId, gsi.ProvisionedThroughput, maskSecrets)) {
19436
- update.ProvisionedThroughput = gsi.ProvisionedThroughput;
19514
+ update.ProvisionedThroughput = this.indexCapacityForSend(this.indexScopeAt(name, physicalId, maskSecrets), gsi.ProvisionedThroughput, maskSecrets);
19437
19515
  updateHasMember = true;
19438
19516
  }
19439
19517
  }
@@ -19465,45 +19543,201 @@ var DynamoDBTableProvider = class {
19465
19543
  return coerced.spec;
19466
19544
  }
19467
19545
  /**
19468
- * One `CreateTable` index entry with its `WarmThroughput` coerced (PR review
19469
- * round 6).
19546
+ * `GSI <name> on AWS::DynamoDB::Table <logicalId>` — the scope every
19547
+ * create-path per-index warning interpolates.
19548
+ *
19549
+ * The index NAME is a RESOLVED property value, so it is masked before it
19550
+ * becomes part of a message (issue #1997). It is also an UNCHECKED cast off
19551
+ * the template: `IndexName: 2024` (unquoted YAML) reaches here as a NUMBER,
19552
+ * and the real masker is a `String.prototype.replace` call that THROWS on
19553
+ * one — which would take the whole deploy down from a diagnostic path. So
19554
+ * the type test is the guard, not merely `!== undefined`: anything that is
19555
+ * not a string has no name to print and takes the `<unnamed>` arm, exactly
19556
+ * as an absent one does. (Reached by the review of issue
19557
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255): before it, the scope
19558
+ * was built only for an entry declaring `WarmThroughput`, so the crash was
19559
+ * unreachable for an ordinary PROVISIONED template.)
19560
+ */
19561
+ indexScope(indexName, logicalId, maskSecrets) {
19562
+ return `GSI ${typeof indexName === "string" ? maskSecrets(indexName) : "<unnamed>"} on AWS::DynamoDB::Table ${logicalId}`;
19563
+ }
19564
+ /**
19565
+ * `GSI <name> on DynamoDB table <physicalId>` — {@link indexScope}'s twin for
19566
+ * the UPDATE path, which names the LIVE table rather than the template's
19567
+ * logical id.
19568
+ *
19569
+ * Same guard, same reason, and the reason is why this exists at all rather
19570
+ * than the five call sites spelling `maskSecrets(name)` inline: `name` is a
19571
+ * key of a map built from `g.IndexName!` off an UNCHECKED template cast, so
19572
+ * `IndexName: 2024` (unquoted YAML) is truthy, becomes the key, and arrives
19573
+ * here as a NUMBER — on which the real masker's `String.prototype.replace`
19574
+ * THROWS. The create path was guarded when {@link indexScope} was added
19575
+ * (the review of issue #3255); the update sites that now build a scope
19576
+ * EAGERLY for the capacity coercion were not, which newly exposed the same
19577
+ * crash on an ordinary PROVISIONED capacity update with a non-empty secret
19578
+ * bag (found by the review of that round).
19579
+ */
19580
+ indexScopeAt(indexName, physicalId, maskSecrets) {
19581
+ return `GSI ${typeof indexName === "string" ? maskSecrets(indexName) : "<unnamed>"} on DynamoDB table ${physicalId}`;
19582
+ }
19583
+ /**
19584
+ * The per-index `ProvisionedThroughput` an `UpdateTable` action should put on
19585
+ * the wire (the review of issue
19586
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255)).
19587
+ *
19588
+ * Exists because coercing only `create()`'s forward left the file
19589
+ * INTERNALLY divergent in the direction the issue is about: the same index
19590
+ * entry succeeded on a fresh create and was rejected by AWS when added by a
19591
+ * later update, which is the `WarmThroughput` round-6 story with the
19592
+ * polarity flipped. All three `applyGsiUpdates` send sites — the adopted
19593
+ * index's repair `Update`, the `Create` action and the same-name `Update`
19594
+ * action — now read through {@link coerceIndexCapacityBlock}, the same
19595
+ * reader `create()` uses.
19596
+ *
19597
+ * **The GUARDS around those sites deliberately stay on `toFiniteNumber`**,
19598
+ * and that is the split `dynamodb-warm-throughput.ts`'s header already
19599
+ * records: a FORWARDER reads CloudFormation's grammar, a GUARD reads the
19600
+ * wider one, because over-acceptance is the safe side of an
19601
+ * already-matches / zero-capacity / change-detection test. Nothing moves as
19602
+ * a result — `liveCapacityAlreadyMatches` and `skipZeroCapacityIndexUpdate`
19603
+ * are asked about the DECLARED block exactly as before, and the change
19604
+ * detector still compares the declared block against the recorded one, both
19605
+ * raw. Only what reaches AWS changes.
19606
+ */
19607
+ indexCapacityForSend(scope, declared, maskSecrets) {
19608
+ if (declared === void 0) return void 0;
19609
+ return this.coerceIndexCapacityBlock(scope, declared, maskSecrets) ?? declared;
19610
+ }
19611
+ /**
19612
+ * One `CreateTable` index entry with BOTH of its throughput blocks coerced —
19613
+ * `WarmThroughput` (PR review round 6) and `ProvisionedThroughput` (issue
19614
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255)).
19470
19615
  *
19471
19616
  * `create()` forwards the declared `GlobalSecondaryIndexes` array to
19472
- * `CreateTable`, so it is a SEND SITE for the same per-index value the
19473
- * update path coerces — and it was the one the round-5 sweep missed. Entries
19474
- * are rebuilt rather than mutated: the input bag belongs to the caller (the
19475
- * resolved template, which the engine also records into state), and a
19476
- * provider that edits it in place would change what state reports cdkd sent.
19477
- *
19478
- * An entry that is not a plain object, or that declares no `WarmThroughput`,
19479
- * is returned UNCHANGED — including the identity of the object, so the
19480
- * common case allocates nothing.
19481
- */
19482
- coerceIndexWarmThroughputForCreate(logicalId, entry, maskSecrets = (text) => text) {
19617
+ * `CreateTable`, so it is a SEND SITE for the same per-index values the
19618
+ * update path coerces. `WarmThroughput` was the one the round-5 sweep
19619
+ * missed; `ProvisionedThroughput` was missed by round 6 AND by issue #3147's
19620
+ * own enumeration, because this forwarder is a CAST rather than a call, so
19621
+ * neither sweep's grep could see it. The BillingMode flip's per-index reader
19622
+ * ({@link readCapacityNumber}) has been on the grammar since #3147, so until
19623
+ * now create and flip disagreed about the same property: a CFn-legal
19624
+ * `ReadCapacityUnits: '7'` reached `CreateTable` as a STRING in a `number`
19625
+ * field, and a `' 7 '` CloudFormation refuses the template for was forwarded
19626
+ * unchanged.
19627
+ *
19628
+ * Entries are rebuilt rather than mutated: the input bag belongs to the
19629
+ * caller (the resolved template, which the engine also records into state),
19630
+ * and a provider that edits it in place would change what state reports cdkd
19631
+ * sent.
19632
+ *
19633
+ * An entry that is not a plain object, and one declaring NEITHER throughput
19634
+ * block, is returned UNCHANGED — identity included. An entry that DOES
19635
+ * declare one is rebuilt whenever that block's coercion changes anything;
19636
+ * the `WarmThroughput` half rebuilds even for an already-numeric value,
19637
+ * which is pre-existing behaviour and costs one spread per declared block.
19638
+ */
19639
+ coerceIndexThroughputForCreate(logicalId, entry, maskSecrets = (text) => text) {
19483
19640
  if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
19484
- const declared = entry["WarmThroughput"];
19485
- if (declared === void 0) return entry;
19486
- const scope = `GSI ${entry.IndexName === void 0 ? "<unnamed>" : maskSecrets(entry.IndexName)} on AWS::DynamoDB::Table ${logicalId}`;
19487
- const coerced = this.coerceWarmThroughputForSend(scope, declared, maskSecrets);
19488
- if (coerced === void 0) {
19489
- this.warnRefusedWarmThroughput(scope, declared, maskSecrets);
19490
- const { WarmThroughput: _dropped, ...rest } = entry;
19491
- return rest;
19641
+ const bag = entry;
19642
+ const declaredWarm = bag["WarmThroughput"];
19643
+ const declaredCapacity = bag["ProvisionedThroughput"];
19644
+ if (declaredWarm === void 0 && !isPlainCapacityBlock(declaredCapacity)) return entry;
19645
+ const scope = this.indexScope(entry.IndexName, logicalId, maskSecrets);
19646
+ let out = entry;
19647
+ if (declaredWarm !== void 0) {
19648
+ const coerced = this.coerceWarmThroughputForSend(scope, declaredWarm, maskSecrets);
19649
+ if (coerced === void 0) {
19650
+ this.warnRefusedWarmThroughput(scope, declaredWarm, maskSecrets);
19651
+ const { WarmThroughput: _dropped, ...rest } = out;
19652
+ out = rest;
19653
+ } else out = {
19654
+ ...out,
19655
+ WarmThroughput: coerced
19656
+ };
19492
19657
  }
19493
- return {
19494
- ...entry,
19495
- WarmThroughput: coerced
19658
+ const capacity = this.coerceIndexCapacityBlock(scope, declaredCapacity, maskSecrets);
19659
+ if (capacity !== void 0) out = {
19660
+ ...out,
19661
+ ProvisionedThroughput: capacity
19496
19662
  };
19663
+ return out;
19497
19664
  }
19498
19665
  /**
19499
- * Announce a TABLE-level `ProvisionedThroughput` member
19500
- * {@link tableCapacityForSend} DROPPED (issue
19501
- * [#3147](https://github.com/go-to-k/cdkd/issues/3147)).
19502
- *
19503
- * Called from BOTH table-level forwarders — `create()`'s `CreateTable` and
19504
- * the BillingMode flip's `UpdateTable` — because the member vanishes at each
19505
- * independently, and a drop that is never announced is the silent-narrowing
19506
- * class this file's other warn arms exist to end.
19666
+ * One `CreateTable` index entry's `ProvisionedThroughput`, with each DECLARED
19667
+ * member read through CloudFormation's DynamoDB Integer grammar (issue
19668
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255)). Returns `undefined`
19669
+ * when the block needs no rewriting at all.
19670
+ *
19671
+ * It reads through {@link readCapacityNumber} — the function the BillingMode
19672
+ * flip's per-index forwarder calls — rather than restating the grammar, for
19673
+ * the same reason {@link hasUsableDeclaredCapacity} does: that is what keeps
19674
+ * create and flip answering identically about one template value.
19675
+ *
19676
+ * THREE decisions, each of which had a plausible alternative:
19677
+ *
19678
+ * - **A rejected spelling drops the MEMBER, not the whole block.** This is
19679
+ * what {@link tableCapacityForSend} already does one level up for the
19680
+ * identical grammar, so create's two capacity forwarders answer the same
19681
+ * way; it keeps a sibling member the template spelled LEGALLY, which
19682
+ * dropping the block would discard; and it is the more informative
19683
+ * failure at AWS — `CreateTable` names the missing member at its exact
19684
+ * index position, where a missing block yields only
19685
+ * `ProvisionedThroughput must be specified for index: <name>`. Both
19686
+ * directions FAIL the deploy (DynamoDB requires both members on a
19687
+ * PROVISIONED index), so this is a choice of error TEXT, never of
19688
+ * outcome — what matters is that neither silently succeeds at a capacity
19689
+ * CloudFormation refuses the template for.
19690
+ *
19691
+ * **That is also why this drop needs NO `effectiveProperties`**, which
19692
+ * `.claude/rules/provider-property-fidelity.md` otherwise requires of any
19693
+ * announced narrowing: the rule exists because a SUCCEEDING call plus a
19694
+ * record describing the declared value is permanent phantom drift. Here
19695
+ * `CreateTable` FAILS, so `create()` never returns and nothing is
19696
+ * recorded — the same reasoning `tableCapacityForSend` states for the
19697
+ * table-level omit one level up.
19698
+ * - **An ABSENT member stays absent — no `5` default.** The per-index
19699
+ * reader deliberately has none ({@link readCapacityNumber}'s doc, issue
19700
+ * #1588): a defaulted per-index capacity would land in cdkd state as if
19701
+ * the template had declared it, with no later call to correct it. Only
19702
+ * the TABLE level defaults.
19703
+ * - **A block that is not a plain object is forwarded VERBATIM**, the
19704
+ * fail-OPEN direction this file takes everywhere: an unresolved intrinsic
19705
+ * or a mis-nested template value is AWS's to reject by name, and that is
19706
+ * also the pre-existing behaviour of this forwarder.
19707
+ *
19708
+ * A member name the grammar does not know is preserved untouched, for the
19709
+ * same fail-open reason — a misspelled `ReadCapacityUnit` reaches AWS and is
19710
+ * named there rather than vanishing here.
19711
+ */
19712
+ coerceIndexCapacityBlock(scope, declared, maskSecrets = (text) => text) {
19713
+ if (!isPlainCapacityBlock(declared)) return void 0;
19714
+ const block = declared;
19715
+ let rewritten;
19716
+ for (const member of PROVISIONED_CAPACITY_MEMBERS) {
19717
+ if (!(member in block)) continue;
19718
+ const coerced = readCapacityNumber(block, member);
19719
+ if (coerced === block[member]) continue;
19720
+ rewritten ??= { ...block };
19721
+ if (coerced === void 0) {
19722
+ this.warnUnusableProvisionedCapacity(scope, member, block[member], maskSecrets);
19723
+ delete rewritten[member];
19724
+ } else rewritten[member] = coerced;
19725
+ }
19726
+ return rewritten;
19727
+ }
19728
+ /**
19729
+ * Announce a `ProvisionedThroughput` member a forwarder DROPPED (issue
19730
+ * [#3147](https://github.com/go-to-k/cdkd/issues/3147); widened to the
19731
+ * per-index create forwarder by issue
19732
+ * [#3255](https://github.com/go-to-k/cdkd/issues/3255)).
19733
+ *
19734
+ * Called from all THREE forwarders that can drop one — `create()`'s
19735
+ * `CreateTable` table-level block, the BillingMode flip's `UpdateTable`
19736
+ * table-level block, and `create()`'s per-`GlobalSecondaryIndexes[]` block —
19737
+ * because the member vanishes at each independently, and a drop that is
19738
+ * never announced is the silent-narrowing class this file's other warn arms
19739
+ * exist to end. `scope` names the resource or the index, so a table with
19740
+ * several GSIs says WHICH one.
19507
19741
  *
19508
19742
  * It says NO capacity was substituted, which is the whole decision: the
19509
19743
  * sibling `AWS::DynamoDB::GlobalTable` forwarders announce a 5/5 FALLBACK,
@@ -19511,9 +19745,9 @@ var DynamoDBTableProvider = class {
19511
19745
  * table that does not exist. {@link tableCapacityForSend}'s doc carries why
19512
19746
  * this type differs.
19513
19747
  */
19514
- warnUnusableTableCapacity(scope, member, raw, maskSecrets = (text) => text) {
19748
+ warnUnusableProvisionedCapacity(scope, member, raw, maskSecrets = (text) => text) {
19515
19749
  const warn = (message) => this.logger.warn(maskSecrets(message));
19516
- warn(`${scope}: ProvisionedThroughput.${member} ${JSON.stringify(maskLeafValue$1(raw, maskSecrets))} is not an Integer CloudFormation accepts (an optional sign and decimal digits only — no surrounding whitespace, hex, exponent or decimal point), so it was NOT sent and NO capacity was substituted for it. DynamoDB requires both members on a PROVISIONED table and will reject the request naming this one — CloudFormation refuses the same template at properties validation. Check for an unresolved intrinsic, or spell the value as a decimal integer.`);
19750
+ warn(`${scope}: ProvisionedThroughput.${member} ${JSON.stringify(maskLeafValue$1(raw, maskSecrets))} is not an Integer CloudFormation accepts (an optional sign and decimal digits only — no surrounding whitespace, hex, exponent or decimal point), so it was NOT sent and NO capacity was substituted for it. DynamoDB requires both members wherever a PROVISIONED ProvisionedThroughput block is declared — the table itself, and every GlobalSecondaryIndexes[] entry of a PROVISIONED table — and will reject the request naming this one; CloudFormation refuses the same template at properties validation. Check for an unresolved intrinsic, or spell the value as a decimal integer.`);
19517
19751
  }
19518
19752
  /**
19519
19753
  * Announce a `WarmThroughput` the send rule REFUSES (PR review of issue
@@ -25748,7 +25982,13 @@ var SSMParameterProvider = class {
25748
25982
  await this.ssmClient.send(new DeleteParameterCommand({ Name: name }));
25749
25983
  debug(`Cleaned up partially-created SSM parameter ${logicalId} (${name}) after wiring failure`);
25750
25984
  } catch (cleanupError) {
25751
- warn(`Failed to clean up partially-created SSM parameter ${logicalId} (${name}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. Manual deletion may be required before the next deploy: aws ssm delete-parameter --name '${name}'`);
25985
+ const deleteCommand = renderDisableCommand({
25986
+ before: "aws ssm delete-parameter --name",
25987
+ identifier: name,
25988
+ maskSecrets: mask
25989
+ });
25990
+ const manualStep = deleteCommand ? `Manual deletion may be required before the next deploy: ${deleteCommand}` : "Manual deletion may be required before the next deploy, via the console: the parameter name cannot be reproduced safely on a command line, and a command naming the sanitized form would delete a DIFFERENT parameter.";
25991
+ warn(`Failed to clean up partially-created SSM parameter ${logicalId} (${name}): ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}. ${manualStep}`);
25752
25992
  }
25753
25993
  throw innerError;
25754
25994
  }
@@ -25976,7 +26216,13 @@ var SSMParameterProvider = class {
25976
26216
  const shape = isArn ? "an ARN" : "a version / label selector";
25977
26217
  const remedy = input.knownPhysicalId === explicit ? `pass the parameter NAME instead: --resource ${input.logicalId}=<parameterName>` : `set Properties.Name to the parameter NAME rather than ${shape}`;
25978
26218
  const whyNotDerived = isArn ? " cdkd deliberately does NOT derive the name from the ARN: the leading \"/\" is not recoverable (arn:...:parameter/foo is the ARN of BOTH the simple name \"foo\" and the hierarchical name \"/foo\"), and a parameter shared from another account has no name form at all — so a derived name could silently address a DIFFERENT parameter." : "";
25979
- throw new ProvisioningError(`Cannot adopt SSM parameter ${input.logicalId} from ${shape} ('${explicit}'): cdkd records a parameter's NAME as its physical id, because SSM's write APIs accept only the name (PutParameter and DeleteParameter both reject an ARN, and a name cannot contain ':'), so the next cdkd deploy and cdkd destroy would fail with a ValidationException. ${remedy}.${whyNotDerived} Read the name AWS holds with: aws ssm get-parameter --name '${explicit}' --query Parameter.Name --output text`, input.resourceType, input.logicalId);
26219
+ const readCommand = renderDisableCommand({
26220
+ before: "aws ssm get-parameter --name",
26221
+ identifier: explicit,
26222
+ after: "--query Parameter.Name --output text"
26223
+ });
26224
+ const howToRead = readCommand ? ` Read the name AWS holds with: ${readCommand}` : " Read the name AWS holds via the console: the value cdkd was given cannot be reproduced safely on a command line, so any command shown here would read a different parameter.";
26225
+ throw new ProvisioningError(`Cannot adopt SSM parameter ${input.logicalId} from ${shape} ('${explicit}'): cdkd records a parameter's NAME as its physical id, because SSM's write APIs accept only the name (PutParameter and DeleteParameter both reject an ARN, and a name cannot contain ':'), so the next cdkd deploy and cdkd destroy would fail with a ValidationException. ${remedy}.${whyNotDerived}${howToRead}`, input.resourceType, input.logicalId);
25980
26226
  }
25981
26227
  /**
25982
26228
  * Adopt an existing SSM parameter into cdkd state.
@@ -66054,7 +66300,7 @@ function indexNestedChildTemplates(template, templatePath) {
66054
66300
  if (resource?.Type !== "AWS::CloudFormation::Stack") continue;
66055
66301
  const assetPath = resource.Metadata?.["aws:asset:path"];
66056
66302
  if (typeof assetPath !== "string" || assetPath.length === 0) continue;
66057
- if (isAbsoluteCrossPlatform(assetPath)) throw new Error(`Nested stack '${logicalId}' has Metadata['aws:asset:path']='${assetPath}' which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
66303
+ if (isAbsoluteCrossPlatform(assetPath)) throw new Error(`Nested stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']='${displaySafe(assetPath)}' which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
66058
66304
  result[logicalId] = path.join(dir, assetPath);
66059
66305
  }
66060
66306
  return result;
@@ -66354,7 +66600,7 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
66354
66600
  * fail at deploy time.
66355
66601
  */
66356
66602
  async function buildDiffTree(args) {
66357
- const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption } = args;
66603
+ const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parentHasSecretReference, parameters, canonicalizeProperties, assetRedirect, cfnFallback, previewOrphanAdoption, ancestorTemplatePaths } = args;
66358
66604
  const state = await loadStateOrEmpty(stackName, region, stateBackend);
66359
66605
  const secretBearingAbove = parentHasSecretReference === true || templateHasSecretDynamicReference(template);
66360
66606
  const { changes, outputChanges, adoptedOrphans, adoptedRecords, blocking } = await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, {
@@ -66390,6 +66636,9 @@ async function buildDiffTree(args) {
66390
66636
  const childTemplatePath = nestedTemplates[logicalId];
66391
66637
  if (!childTemplatePath) throw new Error(`Nested template file not found for ${NESTED_STACK_RESOURCE_TYPE} '${logicalId}' under stack '${stackName}'. Verify the synth output emits Metadata['aws:asset:path'] on this resource (CDK 2.x cdk.NestedStack does so by default), then re-run synth.`);
66392
66638
  const childStackName = `${stackName}~${logicalId}`;
66639
+ const resolvedChildPath = path.resolve(childTemplatePath);
66640
+ if (ancestorTemplatePaths?.has(resolvedChildPath) === true) throw new Error(`Nested stack '${displaySafe(logicalId)}' under stack '${displaySafe(stackName)}' resolves to nested template '${displaySafe(resolvedChildPath)}', which is already being diffed higher up the same nesting chain. Its Metadata['aws:asset:path'] closes a cycle; CDK emits an acyclic nested template tree, so this indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to diff.`);
66641
+ const childAncestorTemplatePaths = new Set(ancestorTemplatePaths ?? []).add(resolvedChildPath);
66393
66642
  const childTemplate = readNestedTemplate(childTemplatePath);
66394
66643
  if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
66395
66644
  const grandchildTemplates = indexNestedChildTemplates(childTemplate, childTemplatePath);
@@ -66408,6 +66657,7 @@ async function buildDiffTree(args) {
66408
66657
  ...assetRedirect && { assetRedirect },
66409
66658
  ...cfnFallback !== void 0 && { cfnFallback },
66410
66659
  ...previewOrphanAdoption && { previewOrphanAdoption },
66660
+ ancestorTemplatePaths: childAncestorTemplatePaths,
66411
66661
  parentHasSecretReference: secretBearingAbove
66412
66662
  }));
66413
66663
  }
@@ -74649,6 +74899,39 @@ function createStateMigrateCommand() {
74649
74899
  //#endregion
74650
74900
  //#region src/cli/commands/state-list-tree.ts
74651
74901
  /**
74902
+ * How deep a node may sit below its root, counting the root as 0.
74903
+ *
74904
+ * A node that would sit deeper is made a root instead (see
74905
+ * {@link buildStackTree}). The value is far beyond any nesting an app is
74906
+ * written with: CloudFormation stops at five levels of nested stacks, so an
74907
+ * app that also has to deploy through CloudFormation cannot exceed that.
74908
+ * cdkd deploys nested stacks through its own engine and imposes no limit of
74909
+ * its own, so a deliberately deeper app CAN produce a tree this reshapes —
74910
+ * which is the point. The view stays whole either way.
74911
+ *
74912
+ * It exists because every step AFTER the tree is built recurses once per
74913
+ * level: this module's own child sort, the box-drawing renderer and
74914
+ * {@link stackTreeToJson}, plus the `JSON.stringify` that serializes that
74915
+ * shape for `--tree --json`. Capping the tree bounds all four at once, which
74916
+ * an iterative rewrite of any of them could not: `JSON.stringify` recurses
74917
+ * into whatever nesting it is handed, and the text view indents four
74918
+ * characters per level, so a chain deep enough would need more indentation
74919
+ * than a string can hold.
74920
+ *
74921
+ * It bounds the DEPTH, not the bucket, and what it does to the failure at the
74922
+ * far end is change its KIND. Before the cap, one deep chain exhausted the
74923
+ * CALL STACK, at a record count nothing fixes — V8's budget moves with frame
74924
+ * size and with what else is on the stack. After it, every recursion is at
74925
+ * most a hundred frames and the remaining ceiling is V8's maximum STRING
74926
+ * length: a capped tree still costs a roughly constant number of characters
74927
+ * per record (the cap's own pretty-print indent dominates in `--tree --json`),
74928
+ * so `JSON.stringify` refuses a large enough bucket deterministically instead
74929
+ * of at a moving depth. Both are far past `renderTreeMode`'s unbounded
74930
+ * one-read-per-reference fan-out, which gives out first. Read the cap as what
74931
+ * stops a SINGLE chain taking the view down, not as a total bound.
74932
+ */
74933
+ const MAX_STACK_TREE_DEPTH = 100;
74934
+ /**
74652
74935
  * Build a parent → child tree from the flat list of state records.
74653
74936
  *
74654
74937
  * Children are linked to their parent by `(parentStack, parentRegion)`
@@ -74664,6 +74947,17 @@ function createStateMigrateCommand() {
74664
74947
  * from both the text and the JSON view. A node hanging off a loop still sits
74665
74948
  * under its parent, which is now visible (issue #3069).
74666
74949
  *
74950
+ * A node that would sit deeper than {@link MAX_STACK_TREE_DEPTH} levels below
74951
+ * its root is placed at the root too, so the chain resumes there as its own
74952
+ * tree. Every record still appears exactly once, and a tree within the cap is
74953
+ * built as before. Without it a long enough chain took the whole view down with
74954
+ * a `RangeError` instead of costing only its own rows, since the recursive
74955
+ * steps AFTER this function are not covered by the per-stack guard around the
74956
+ * state read. Reaching the depths that failed takes thousands of records — in
74957
+ * practice a hand-edited bucket, or records planted by someone holding
74958
+ * `s3:PutObject` on it — but nothing in cdkd bounds the depth, so the cap does
74959
+ * (issue #3155).
74960
+ *
74667
74961
  * The roots and every child list are sorted alphabetically by `stackName`,
74668
74962
  * then by `region` (legacy `undefined` last), so output is stable across
74669
74963
  * runs.
@@ -74690,10 +74984,26 @@ function buildStackTree(entries) {
74690
74984
  if (current !== void 0 && onPath.has(current)) for (const member of path.slice(onPath.get(current))) loopMembers.add(member);
74691
74985
  for (const node of path) settled.add(node);
74692
74986
  }
74987
+ const depthOf = /* @__PURE__ */ new Map();
74988
+ for (const member of loopMembers) depthOf.set(member, 0);
74989
+ for (const start of byKey.values()) {
74990
+ if (depthOf.has(start)) continue;
74991
+ const path = [];
74992
+ let current = start;
74993
+ while (current !== void 0 && !depthOf.has(current)) {
74994
+ path.push(current);
74995
+ current = parentOf(current);
74996
+ }
74997
+ let depth = current === void 0 ? -1 : depthOf.get(current);
74998
+ for (let i = path.length - 1; i >= 0; i--) {
74999
+ depth = depth + 1 > 100 ? 0 : depth + 1;
75000
+ depthOf.set(path[i], depth);
75001
+ }
75002
+ }
74693
75003
  const roots = [];
74694
75004
  for (const node of byKey.values()) {
74695
75005
  const parent = parentOf(node);
74696
- if (parent && !loopMembers.has(node)) {
75006
+ if (parent && depthOf.get(node) > 0) {
74697
75007
  parent.children.push(node);
74698
75008
  continue;
74699
75009
  }
@@ -78727,6 +79037,12 @@ async function stateListCommand(options) {
78727
79037
  *
78728
79038
  * `tree --json` emits the nested {@link import('./state-list-tree.js').StackTreeJson}
78729
79039
  * shape; plain `tree` renders `tree(1)`-style box-drawing.
79040
+ *
79041
+ * The `JSON.stringify` below recurses once per level of the shape it is handed,
79042
+ * and so does the box-drawing renderer. Neither needs a depth guard here:
79043
+ * `buildStackTree` caps how deep a node may sit
79044
+ * ({@link import('./state-list-tree.js').MAX_STACK_TREE_DEPTH}), which bounds
79045
+ * both (issue #3155).
78730
79046
  */
78731
79047
  async function renderTreeMode(refs, stateBackend, asJson) {
78732
79048
  const entries = await Promise.all(refs.map(async (ref) => {
@@ -94172,4 +94488,4 @@ function buildProgram() {
94172
94488
 
94173
94489
  //#endregion
94174
94490
  export { buildProgram };
94175
- //# sourceMappingURL=program-BBgCW2BX.js.map
94491
+ //# sourceMappingURL=program-CbHndlOj.js.map