@go-to-k/cdkd 0.288.1 → 0.288.3

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 createMaskedRetryLogger, $n as runDockerStreaming, $r as StackTerminationProtectionError, $t as carriesSecretMask, A as WARM_THROUGHPUT_MEMBERS, An as rewriteTemplateAssetReferences, At as replayWarn, B as yellow, Bn as validateContainerRepoName, Bt as s3BucketWebsiteUrl, C as unsupportedFinalSnapshotError, Cn as shouldRetainResource, Cr as displaySafe, Ct as normalizeAwsTagsToCfn, D as isStatefulRecreateTargetForReplace, Dn as buildAssetRedirectMap, Dr as derivePartitionAndUrlSuffix, Dt as configBooleanRefusal, E as MULTI_REGION_RECREATE_BLOCKED_TYPES, En as WorkGraph, Er as canonicalizeRegion, Et as coerceCfnBoolean, F as bold, Fn as getBootstrapMarkerKey, Fr as setAwsClients, Ft as producerRegionsFromState, G as secretBearingStateKeyWarning, Gn as describeDockerExecFailure, Gr as LocalStartServiceError, Gt as findSilentDropProperties, H as collectPublishedOutputNames, Hn as describeAwsFailure, Hr as DynamicReferenceRegionAmbiguousError, Ht as DiffCalculator, I as cyan, In as isCrossRegionRedirect, It as s3BucketArn, J as IAMRoleProvider, Jn as formatDockerLoginError, Jr as PartialFailureError, Jt as DagBuilder, K as stateKeySecretExposure, Kn as describeDockerFailure, Kt as describeTypeWithThrottleRetry, L as gray, Ln as parseBootstrapMarker, Lr as CdkdError, Lt as s3BucketDomainName, M as isWarmThroughputDecrease, Mn as BOOTSTRAP_MARKER_PREFIX, Mr as AwsClients, Mt as requireConfigObject, N as toFiniteNumber, Nn as assertAssetBucketRegion, Nr as getAwsClients, Nt as requireConfigString, O as isStatefulRecreateTargetSync, On as createAssetRedirectResolver, Or as AssemblyReader, Ot as configStringRefusal, P as formatResourceLine, Pn as ensureAssetStorage, Pr as resetAwsClients, Pt as classifyReplaySecretRegion, Q as wouldReturnToSdkProvider, Qn as runDockerForeground, Qr as StackHasActiveImportsError, Qt as TEMPLATE_SOURCED_RULES, R as green, Rn as readBootstrapMarkerBody, Rt as s3BucketDualStackDomainName, S as refusesFinalSnapshot, Sn as importableOutputs, Sr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, St as WAFv2WebACLProvider, T as extractDeploymentEventError, Tn as stringifyValue, Tr as PARTITION_TABLE, Tt as assertRegionMatch, U as exportAliasCollisionScrubWarning, Un as buildDockerImage, Ur as IntrinsicResolutionRefusalError, Ut as INTRINSIC_KEYS, V as collectDeclaredOutputNames, Vn as buildDenyExternalAccessPolicy, Vr as DeployCancelledError, Vt as applyRoleArnIfSet, W as isExportAliasCollision, Wn as describeDockerCapturedOutput, Wr as LocalInvokeBuildError$1, Wt as findActionableSilentDrops, X as clearOnUpdateRemoval, Xn as partitionSensitiveEnv, Xr as ResourceTimeoutError, Xt as STATE_SOURCED_CROSS_GENERATION_RULES, Y as collectInlinePolicyNamesManagedBySiblings, Yn as getDockerCmd, Yr as ProvisioningError, Yt as TemplateParser, Z as ProviderRegistry, Zn as redactDockerArgvValues, Zr as ResourceUpdateNotSupportedError, Zt as STATE_SOURCED_READBACK_RULES, _ as PRE_DELETE_SNAPSHOT_TYPES, _n as shellQuote, _r as CFN_TEMPLATE_BODY_LIMIT, _t as coerceParameterTypedValue, a as DeploymentEventsStore, ai as withErrorHandling, an as maskSecretsInText, ar as synthesisStatusMessage, at as isInterruptedWaitError, b as createPreDeleteFinalSnapshot, bn as exportNamesCarriedFrom, br as findLargeInlineResources, bt as parameterTypeMayLoseSecretIdentity, c as replayFailedOperations, ci as isThrottlingError, cn as redactSecretsForState, cr as resolveApp, ct as slowCcOperationTimeoutMs, d as updatePartialReason, di as markRedactedCause, dn as S3StateBackend, dr as resolveSkipPrefix, dt as deleteSkipReason, en as createSecretMasker, er as escapeRegExp, et as maskDeep, f as withResourceDeadline, fi as retryClassificationText, fn as rebuildClientForBucketRegion, fr as resolveStateBucketWithDefault, ft as disableInstanceApiTermination, g as ATOMIC_FINAL_SNAPSHOT_TYPES, gn as forceQuitRecoveryClause, gr as warnDeprecatedNoPrefixCliFlag, gt as cfnRefValueFromPhysicalId, h as computeImplicitDeleteEdges, hn as buildLockContentionMessage, hr as stateBucketExistenceConfirmed, ht as carriesDynamicReference, i as DeploymentEventsReader, ii as normalizeAwsError, in as maskSecretsInError, ir as Synthesizer, it as interruptWatchListenerCount, j as coerceWarmThroughput$1, jn as AssetModeResolver, jr as resolveBucketRegion, jt as requireConfigArray, k as renderStatefulReason, kn as loadPublishableAssetManifest, kr as processStackMessages, kt as readConfigString, l as replayRollback, li as isTransientServerError, ln as scrubResourceRecord, lr as resolveAutoAssetStorage, lt as UNSPECIFIED_SKIP_REASON, m as IMPLICIT_DELETE_DEPENDENCIES, mn as buildForceUnlockCommand, mr as resolveUseCdkBootstrapAssets, mt as IntrinsicFunctionResolver, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as errorCauseChain, nr as AssetManifestLoader, nt as beginCommandInterruptScope, o as planFailedOps, oi as isMarkedNonRetryable, on as recordMaskOnlyValue, or as getDefaultStateBucketName, ot as startInterruptWatch, p as maskingRetryLogger, pn as UNRENDERABLE, pr as resolveStateBucketWithDefaultAndSource, pt as isTerminationProtectionPropagationError, q as getCurrentResourceSecrets, qn as dockerSpawnEnvWithSensitive, qr as NestedStackChildDirectDestroyError, qt as withRetry, r as DeployEngine, rn as isSingleDynamicReferenceToken, rr as getDockerImageBySourceHash, rt as endCommandInterruptScope, s as planRollback, si as isRetryableTransientError, sn as recoverMaskedOutput, sr as getLegacyStateBucketName, st as CloudControlProvider, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as dynamicReferenceTokens, tr as stripControlChars, tt as maskerOrIdentity, u as updatePartialMessage, ui as markNonRetryable, un as LockManager, ur as resolveCaptureObservedState, ut as deleteIndeterminateGuards, v as buildFinalSnapshotIdentifier, vn as CUSTOM_RESOURCE_RESPONSE_PREFIX, vr as CFN_TEMPLATE_URL_LIMIT, vt as getAccountInfo, w as makeCanonicalizePropertiesFn, wn as AssetPublisher, wr as expectedOwnerParam, wt as resolveExplicitPhysicalId, x as isFinalSnapshotError, xn as importableOutputKeys, xr as uploadCfnTemplate, xt as refStateLookupFromResource, y as ccRoutedFinalSnapshotError, yn as DEFAULT_STATE_PREFIX, yr as MIGRATE_TMP_PREFIX, yt as isUnboundTemplateParameter, z as red, zn as validateAssetBucketName, zr as CrossAccountSecretRefusalError, zt as s3BucketRegionalDomainName } from "./deploy-engine-ZPkBIilF.js";
1
+ import { $ as ProviderRegistry, $n as redactDockerArgvValues, $r as ResourceUpdateNotSupportedError, $t as STATE_SOURCED_READBACK_RULES, A as renderStatefulReason, An as createAssetRedirectResolver, Ar as AssemblyReader, At as configStringRefusal, B as red, Bn as readBootstrapMarkerBody, Bt as s3BucketDualStackDomainName, C as refusesFinalSnapshot, Cn as importableOutputKeys, Cr as uploadCfnTemplate, Ct as refStateLookupFromResource, D as MULTI_REGION_RECREATE_BLOCKED_TYPES, Dn as stringifyValue, Dr as PARTITION_TABLE, Dt as assertRegionMatch, E as extractDeploymentEventError, En as AssetPublisher, Er as expectedOwnerParam, Et as resolveExplicitPhysicalId, F as formatResourceLine, Fn as assertAssetBucketRegion, Fr as getAwsClients, Ft as requireConfigString, G as isExportAliasCollision, Gn as buildDockerImage, Gr as IntrinsicResolutionRefusalError, Gt as INTRINSIC_KEYS, H as collectDeclaredOutputNames, Hn as validateContainerRepoName, Ht as s3BucketWebsiteUrl, I as bold, In as ensureAssetStorage, Ir as resetAwsClients, It as classifyReplaySecretRegion, J as stateKeySecretExposure, Jn as describeDockerFailure, Jt as describeTypeWithThrottleRetry, K as secretBearingStateKeyWarning, Kn as describeDockerCapturedOutput, Kr as LocalInvokeBuildError$1, Kt as findActionableSilentDrops, L as cyan, Ln as getBootstrapMarkerKey, Lr as setAwsClients, Lt as producerRegionsFromState, M as coerceWarmThroughput$1, Mn as rewriteTemplateAssetReferences, Mt as replayWarn, N as isWarmThroughputDecrease, Nn as AssetModeResolver, Nr as resolveBucketRegion, Nt as requireConfigArray, O as isStatefulRecreateTargetForReplace, On as WorkGraph, Or as canonicalizeRegion, Ot as coerceCfnBoolean, P as toFiniteNumber, Pn as BOOTSTRAP_MARKER_PREFIX, Pr as AwsClients, Pt as requireConfigObject, Q as clearOnUpdateRemoval, Qn as partitionSensitiveEnv, Qr as ResourceTimeoutError, Qt as STATE_SOURCED_CROSS_GENERATION_RULES, R as gray, Rn as isCrossRegionRedirect, Rt as s3BucketArn, S as isFinalSnapshotError, Sn as exportNamesCarriedFrom, Sr as findLargeInlineResources, St as parameterTypeMayLoseSecretIdentity, T as makeCanonicalizePropertiesFn, Tn as shouldRetainResource, Tr as displaySafe, Tt as normalizeAwsTagsToCfn, U as collectPublishedOutputNames, Un as buildDenyExternalAccessPolicy, Ur as DeployCancelledError, Ut as applyRoleArnIfSet, V as yellow, Vn as validateAssetBucketName, Vr as CrossAccountSecretRefusalError, Vt as s3BucketRegionalDomainName, W as exportAliasCollisionScrubWarning, Wn as describeAwsFailure, Wr as DynamicReferenceRegionAmbiguousError, Wt as DiffCalculator, X as IAMRoleProvider, Xn as formatDockerLoginError, Xr as PartialFailureError, Xt as DagBuilder, Y as getCurrentResourceSecrets, Yn as dockerSpawnEnvWithSensitive, Yr as NestedStackChildDirectDestroyError, Yt as withRetry, Z as collectInlinePolicyNamesManagedBySiblings, Zn as getDockerCmd, Zr as ProvisioningError, Zt as TemplateParser, _ as ATOMIC_FINAL_SNAPSHOT_TYPES, _n as buildLockContentionMessage, _r as stateBucketExistenceConfirmed, _t as carriesDynamicReference, a as DeploymentEventsStore, an as isSingleDynamicReferenceToken, ar as getDockerImageBySourceHash, at as endCommandInterruptScope, b as ccRoutedFinalSnapshotError, bn as CUSTOM_RESOURCE_RESPONSE_PREFIX, br as CFN_TEMPLATE_URL_LIMIT, bt as getAccountInfo, c as replayFailedOperations, ci as isMarkedNonRetryable, cn as recordMaskOnlyValue, cr as getDefaultStateBucketName, ct as startInterruptWatch, d as updatePartialReason, di as isTransientServerError, dn as scrubResourceRecord, dr as resolveAutoAssetStorage, dt as UNSPECIFIED_SKIP_REASON, ei as StackHasActiveImportsError, en as TEMPLATE_SOURCED_RULES, er as runDockerForeground, et as wouldReturnToSdkProvider, f as withResourceDeadline, fi as markNonRetryable, fn as LockManager, fr as resolveCaptureObservedState, ft as deleteIndeterminateGuards, g as bindingSkippedOutputs, gn as buildForceUnlockCommand, gr as resolveUseCdkBootstrapAssets, gt as IntrinsicFunctionResolver, h as computeImplicitDeleteEdges, hn as UNRENDERABLE, hr as resolveStateBucketWithDefaultAndSource, ht as isTerminationProtectionPropagationError, i as DeploymentEventsReader, in as errorCauseChain, ir as AssetManifestLoader, it as beginCommandInterruptScope, j as WARM_THROUGHPUT_MEMBERS, jn as loadPublishableAssetManifest, jr as processStackMessages, jt as readConfigString, k as isStatefulRecreateTargetSync, kn as buildAssetRedirectMap, kr as derivePartitionAndUrlSuffix, kt as configBooleanRefusal, l as replayRollback, li as isRetryableTransientError, ln as recoverMaskedOutput, lr as getLegacyStateBucketName, lt as CloudControlProvider, m as IMPLICIT_DELETE_DEPENDENCIES, mi as retryClassificationText, mn as rebuildClientForBucketRegion, mr as resolveStateBucketWithDefault, mt as disableInstanceApiTermination, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as createSecretMasker, nr as escapeRegExp, nt as maskDeep, o as planFailedOps, oi as normalizeAwsError, on as maskSecretsInError, or as Synthesizer, ot as interruptWatchListenerCount, p as maskingRetryLogger, pi as markRedactedCause, pn as S3StateBackend, pr as resolveSkipPrefix, pt as deleteSkipReason, q as secretSafeKeyDisplay, qn as describeDockerExecFailure, qr as LocalStartServiceError, qt as findSilentDropProperties, r as DeployEngine, rn as dynamicReferenceTokens, rr as stripControlChars, rt as maskerOrIdentity, s as planRollback, si as withErrorHandling, sn as maskSecretsInText, sr as synthesisStatusMessage, st as isInterruptedWaitError, t as DEFAULT_RESOURCE_TIMEOUT_MS, ti as StackTerminationProtectionError, tn as carriesSecretMask, tr as runDockerStreaming, tt as createMaskedRetryLogger, u as updatePartialMessage, ui as isThrottlingError, un as redactSecretsForState, ur as resolveApp, ut as slowCcOperationTimeoutMs, v as PRE_DELETE_SNAPSHOT_TYPES, vn as forceQuitRecoveryClause, vr as warnDeprecatedNoPrefixCliFlag, vt as cfnRefValueFromPhysicalId, w as unsupportedFinalSnapshotError, wn as importableOutputs, wr as CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION, wt as WAFv2WebACLProvider, x as createPreDeleteFinalSnapshot, xn as DEFAULT_STATE_PREFIX, xr as MIGRATE_TMP_PREFIX, xt as isUnboundTemplateParameter, y as buildFinalSnapshotIdentifier, yn as shellQuote, yr as CFN_TEMPLATE_BODY_LIMIT, yt as coerceParameterTypedValue, z as green, zn as parseBootstrapMarker, zr as CdkdError, zt as s3BucketDomainName } from "./deploy-engine-CfxcC3q3.js";
2
2
  import { _ as withStackName, c as PATTERN_B_NAME_OPTIONS, f as generateResourceName, g as withSkipPrefix, i as reserveStdoutForPayload, l as PATTERN_B_NAME_PROPERTIES, m as getCurrentStackName, n as getLogger, o as runStackBuffered, p as generateResourceNameWithFallback, s as getLiveRenderer, u as PATTERN_B_RESOURCE_TYPES } from "./logger-Dw-3y48G.js";
3
3
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
4
- import { t as getCdkdVersion } from "./version-CGFRodgE.js";
5
- import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-4QzW_6q5.js";
4
+ import { t as getCdkdVersion } from "./version-CEyZvTcB.js";
5
+ import { r as protectedReplacementAdvice, t as ASGProvider } from "./asg-provider-CQrHElrl.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";
@@ -1780,15 +1780,16 @@ async function bootstrapCommand(options) {
1780
1780
  });
1781
1781
  }
1782
1782
  let skipStateBucketConfig = false;
1783
- if (bucketExists) if (!options.force) {
1784
- if (!options.assets) {
1785
- logger.warn(`Bucket ${bucketName} already exists. Use --force to reconfigure (this will not delete existing state)`);
1786
- return;
1787
- }
1788
- logger.info(`State bucket ${bucketName} already exists — skipping reconfiguration (use --force to reconfigure)`);
1789
- skipStateBucketConfig = true;
1790
- } else logger.info("--force specified, continuing with existing bucket");
1791
- else {
1783
+ if (bucketExists) {
1784
+ if (!options.force) {
1785
+ if (!options.assets) {
1786
+ logger.warn(`Bucket ${bucketName} already exists. Use --force to reconfigure (this will not delete existing state)`);
1787
+ return;
1788
+ }
1789
+ logger.info(`State bucket ${bucketName} already exists — skipping reconfiguration (use --force to reconfigure)`);
1790
+ skipStateBucketConfig = true;
1791
+ } else logger.info("--force specified, continuing with existing bucket");
1792
+ } else {
1792
1793
  logger.info(`Creating S3 bucket: ${bucketName} in region ${region}`);
1793
1794
  const createBucketParams = { Bucket: bucketName };
1794
1795
  if (region !== "us-east-1") createBucketParams.CreateBucketConfiguration = { LocationConstraint: region };
@@ -3469,6 +3470,67 @@ function mapsEqual(a, b) {
3469
3470
  }
3470
3471
  return true;
3471
3472
  }
3473
+ /**
3474
+ * Describe a `JSON.parse` failure WITHOUT echoing the body (issue
3475
+ * [#2667](https://github.com/go-to-k/cdkd/issues/2667) review).
3476
+ *
3477
+ * V8 embeds a window of the INPUT in its parse error, so interpolating
3478
+ * `err.message` republishes whatever bytes sat near the syntax error. Measured
3479
+ * on node v24.19.0:
3480
+ *
3481
+ * ```
3482
+ * JSON.parse('{"exports":{"K":{"value":hunter2SECRET}}}')
3483
+ * -> Unexpected token 'h', ..."K":{"value":hunter2SEC"... is not valid JSON
3484
+ * ```
3485
+ *
3486
+ * This index holds resolved Output VALUES, and for the unscrubbed state
3487
+ * `cdkd scrub` targets those are exactly the plaintext — which then reaches a
3488
+ * `logger.error` and the command's failure message, i.e. `--dry-run --fail` CI
3489
+ * logs. Same class as the `import.ts` parse-snippet leak issue
3490
+ * [#2829](https://github.com/go-to-k/cdkd/issues/2829) closed.
3491
+ *
3492
+ * What survives is the SIZE, plus the OFFSET WHEN V8 STATES ONE — neither is a
3493
+ * function of the body's content, and both help an operator holding the object
3494
+ * locate the damage. The offset is extracted by pattern rather than passed
3495
+ * through, so a message worded differently yields no offset instead of leaking
3496
+ * the rest of the sentence. The two numbers are in DIFFERENT UNITS and the
3497
+ * message says so: V8 counts `at position` in UTF-16 code units while the size
3498
+ * is UTF-8 bytes, so `'{ n\u00f6t json'` reports position 2 and 11 bytes. Measured on node v24.19.0: the `Expected ...`
3499
+ * family carries `at position N` and the `Unexpected token 'X', ...snippet...`
3500
+ * family — the one that embeds the body, i.e. the case this exists for — does
3501
+ * NOT, so the size is what is always present and the offset is a bonus. Both
3502
+ * arms are driven by tests.
3503
+ */
3504
+ function describeParseFailure(err, bytes) {
3505
+ const raw = err instanceof Error ? err.message : String(err);
3506
+ const at = /at position (\d+)/.exec(raw);
3507
+ return `invalid JSON${at ? ` at code-unit position ${at[1]}` : ""}; ${bytes} byte(s) read`;
3508
+ }
3509
+ /**
3510
+ * UTF-8 byte length of a decoded body, for {@link describeParseFailure}.
3511
+ *
3512
+ * `body.length` counts UTF-16 CODE UNITS, so reporting it as "byte(s)" is
3513
+ * wrong for any non-ASCII index — an export name outside the BMP counts 2
3514
+ * where the object holds 4 (issue #2667 review). The number is meant to help
3515
+ * an operator match the message against the object S3 holds, so it has to be
3516
+ * the same unit S3 reports.
3517
+ */
3518
+ function utf8ByteLength(body) {
3519
+ return Buffer.byteLength(body, "utf8");
3520
+ }
3521
+ /**
3522
+ * Describe an `indexVersion` that is not a usable number, WITHOUT rendering it.
3523
+ *
3524
+ * `String(x)` renders a body-controlled value verbatim for two of the three
3525
+ * non-number shapes — measured on node v24.19.0: a JSON string yields itself
3526
+ * and a one-element array yields its element, while only an object degrades to
3527
+ * `[object Object]`. The field is attacker-controlled in the same sense the
3528
+ * rest of the body is, so the message reports its TYPE and, for a number, its
3529
+ * value — the only shape whose value is safe because it is a number.
3530
+ */
3531
+ function describeIndexVersion(value) {
3532
+ return typeof value === "number" ? String(value) : `a non-numeric value (${typeof value})`;
3533
+ }
3472
3534
  const DEFAULT_OPTIONS = {
3473
3535
  maxWriteRetries: 5,
3474
3536
  initialBackoffMs: 100,
@@ -3596,12 +3658,57 @@ var ExportIndexStore = class {
3596
3658
  await this.enqueueWrite("update", () => this.applyStackUpdate(stackName, producerRegion, outputs));
3597
3659
  }
3598
3660
  /**
3661
+ * A snapshot of the persisted index, read WITHOUT the rebuild that
3662
+ * {@link lookup} triggers on a missing or corrupt object.
3663
+ *
3664
+ * Every S3 call THIS METHOD makes is a GET, so a caller that must perform no
3665
+ * S3 write (`cdkd scrub --dry-run`) can read the entries here — with ONE
3666
+ * documented exception, which such a caller has to know about: when another
3667
+ * caller on the same instance already has a `doLoad` in flight, the first
3668
+ * line below awaits IT, and `doLoad` rebuilds (a PUT) on a missing or
3669
+ * corrupt object. Unreachable from `cdkd scrub`, which never calls `lookup`
3670
+ * and drives one store per region serially — but the guarantee is the thing
3671
+ * a future no-write caller would rely on, so it is stated with its bound
3672
+ * rather than unconditionally.
3673
+ *
3674
+ * THREE OUTCOMES, and only ONE of them is `undefined`. A MISSING object
3675
+ * returns `undefined`: there is nothing to audit and nothing to repair. A
3676
+ * body that does not PARSE, and an `indexVersion` this binary cannot
3677
+ * interpret, both THROW — because the caller's question is "what does this
3678
+ * index hold", and for those two the honest answer is "this call could not
3679
+ * find out". Collapsing the corrupt case into `undefined` reads as an empty
3680
+ * index, so a caller reports nothing over bytes it never interpreted, which
3681
+ * for `cdkd scrub` is a green gate over a file that may still hold the
3682
+ * plaintext (issue [#2667](https://github.com/go-to-k/cdkd/issues/2667)) —
3683
+ * the exact shape that issue exists to close, one level down. `doLoad`'s own
3684
+ * corrupt arm is unaffected: it REBUILDS, which is a write, and is why this
3685
+ * read cannot share it.
3686
+ *
3687
+ * The returned map is a COPY, and a `patchEntry` on this same instance
3688
+ * afterwards writes under the etag this read cached rather than re-reading.
3689
+ */
3690
+ async readPersistedEntries() {
3691
+ if (this.loadState.kind === "loading") await this.loadState.promise;
3692
+ if (this.loadState.kind === "loaded") return new Map(this.loadState.entries);
3693
+ const outcome = await this.loadPersisted();
3694
+ if (outcome.kind === "missing") return void 0;
3695
+ if (outcome.kind === "corrupt") throw new Error(`Exports index at ${this.indexKey()} could not be parsed (${describeParseFailure(outcome.error, outcome.bytes)}). Its contents were NOT examined.`);
3696
+ return new Map(outcome.entries);
3697
+ }
3698
+ /**
3599
3699
  * Patch a single entry into the index after a `lookup` miss fell back
3600
3700
  * to a state.json scan and found the value. Lightweight write that
3601
3701
  * does NOT require a full rebuild.
3702
+ *
3703
+ * Returns whether the index now holds `entry` — `false` when the write
3704
+ * was abandoned (a non-retryable S3 failure, or an exhausted If-Match
3705
+ * retry budget), which `runWithRetry` logs and does not throw. A caller
3706
+ * for which an unwritten entry is the outcome it must report — `cdkd
3707
+ * scrub`, where the entry keeps whatever value it held — reads this
3708
+ * return; the deploy path treats the index as a perf hint and ignores it.
3602
3709
  */
3603
- async patchEntry(exportName, entry) {
3604
- await this.enqueueWrite("patch", () => this.applyPatch(exportName, entry));
3710
+ async patchEntry(exportName, entry, opts = {}) {
3711
+ return this.enqueueWrite("patch", () => this.applyPatch(exportName, entry, opts.requireOwner));
3605
3712
  }
3606
3713
  /**
3607
3714
  * Drop all entries owned by `(stackName, producerRegion)`. Used after
@@ -3622,7 +3729,7 @@ var ExportIndexStore = class {
3622
3729
  */
3623
3730
  async enqueueWrite(label, op) {
3624
3731
  const next = this.writeChain.then(() => this.runWithRetry(label, op));
3625
- this.writeChain = next.catch(() => {});
3732
+ this.writeChain = next.then(() => {}, () => {});
3626
3733
  return next;
3627
3734
  }
3628
3735
  /**
@@ -3657,31 +3764,54 @@ var ExportIndexStore = class {
3657
3764
  };
3658
3765
  await promise;
3659
3766
  }
3767
+ /**
3768
+ * GET the index object and, when it parses, install it as `loadState`.
3769
+ * Performs no PUT: the missing / corrupt REBUILD lives one level up in
3770
+ * {@link doLoad}, so {@link readPersistedEntries} can share this parse
3771
+ * without writing. A `indexVersion` above this binary's still throws
3772
+ * here rather than reporting `corrupt` — a rebuild would overwrite an
3773
+ * index a newer binary wrote with this binary's view of it.
3774
+ */
3775
+ async loadPersisted() {
3776
+ const raw = await this.readIndexRaw();
3777
+ if (raw === null) return { kind: "missing" };
3778
+ const { body, etag } = raw;
3779
+ let parsed;
3780
+ try {
3781
+ parsed = JSON.parse(body);
3782
+ } catch (err) {
3783
+ return {
3784
+ kind: "corrupt",
3785
+ error: err,
3786
+ bytes: utf8ByteLength(body)
3787
+ };
3788
+ }
3789
+ if (typeof parsed.indexVersion !== "number" || parsed.indexVersion > 1) throw new Error(`Exports index uses indexVersion ${describeIndexVersion(parsed.indexVersion)} which is newer than this cdkd binary supports (max ${1}). Upgrade cdkd.`);
3790
+ const entries = /* @__PURE__ */ new Map();
3791
+ for (const [name, entry] of Object.entries(parsed.exports ?? {})) entries.set(name, entry);
3792
+ this.loadState = {
3793
+ kind: "loaded",
3794
+ etag,
3795
+ entries
3796
+ };
3797
+ return {
3798
+ kind: "loaded",
3799
+ entries
3800
+ };
3801
+ }
3660
3802
  async doLoad() {
3661
3803
  try {
3662
- const raw = await this.readIndexRaw();
3663
- if (raw === null) {
3804
+ const outcome = await this.loadPersisted();
3805
+ if (outcome.kind === "missing") {
3664
3806
  this.logger.info("Exports index missing; rebuilding from state.json files");
3665
3807
  await this.rebuild();
3666
3808
  return;
3667
3809
  }
3668
- const { body, etag } = raw;
3669
- let parsed;
3670
- try {
3671
- parsed = JSON.parse(body);
3672
- } catch (err) {
3673
- this.logger.warn(`Exports index corrupt (${err instanceof Error ? err.message : String(err)}); rebuilding from state.json files`);
3810
+ if (outcome.kind === "corrupt") {
3811
+ this.logger.warn(`Exports index corrupt (${outcome.error instanceof Error ? outcome.error.message : String(outcome.error)}); rebuilding from state.json files`);
3674
3812
  await this.rebuild();
3675
3813
  return;
3676
3814
  }
3677
- if (typeof parsed.indexVersion !== "number" || parsed.indexVersion > 1) throw new Error(`Exports index uses indexVersion ${String(parsed.indexVersion)} which is newer than this cdkd binary supports (max ${1}). Upgrade cdkd.`);
3678
- const entries = /* @__PURE__ */ new Map();
3679
- for (const [name, entry] of Object.entries(parsed.exports ?? {})) entries.set(name, entry);
3680
- this.loadState = {
3681
- kind: "loaded",
3682
- etag,
3683
- entries
3684
- };
3685
3815
  } catch (err) {
3686
3816
  this.loadState = { kind: "unloaded" };
3687
3817
  throw err;
@@ -3763,7 +3893,7 @@ var ExportIndexStore = class {
3763
3893
  }
3764
3894
  async applyStackUpdate(stackName, producerRegion, outputs) {
3765
3895
  await this.ensureLoaded();
3766
- if (this.loadState.kind !== "loaded") return;
3896
+ if (this.loadState.kind !== "loaded") return false;
3767
3897
  const next = new Map(this.loadState.entries);
3768
3898
  for (const [name, entry] of next) if (entry.producerStack === stackName && entry.producerRegion === producerRegion) next.delete(name);
3769
3899
  for (const [name, value] of Object.entries(outputs)) {
@@ -3774,8 +3904,8 @@ var ExportIndexStore = class {
3774
3904
  producerRegion
3775
3905
  });
3776
3906
  }
3777
- if (mapsEqual(this.loadState.entries, next)) return;
3778
- await this.persist(next);
3907
+ if (mapsEqual(this.loadState.entries, next)) return true;
3908
+ return this.persist(next);
3779
3909
  }
3780
3910
  /**
3781
3911
  * Two stacks publishing one export name (issue #2193). CloudFormation
@@ -3792,27 +3922,34 @@ var ExportIndexStore = class {
3792
3922
  const safeName = displaySafe(exportName);
3793
3923
  this.logger.warn(`Export '${safeName}' is published by both '${existing.producerStack}' (${existing.producerRegion}) and '${stackName}' (${producerRegion}); the exports index keeps the latest writer, so an Fn::ImportValue on it binds to whichever deployed last. CloudFormation refuses a second producer of the same export name — rename one of the two exports. (If '${existing.producerStack}' predates cdkd's state schema v9, this is its stale entry for a plain output of that name, and its next deploy clears it.)`);
3794
3924
  }
3795
- async applyPatch(exportName, entry) {
3925
+ async applyPatch(exportName, entry, requireOwner) {
3796
3926
  await this.ensureLoaded();
3797
- if (this.loadState.kind !== "loaded") return;
3927
+ if (this.loadState.kind !== "loaded") return false;
3928
+ if (requireOwner) {
3929
+ const current = this.loadState.entries.get(exportName);
3930
+ if (!current || current.producerStack !== requireOwner.producerStack || current.producerRegion !== requireOwner.producerRegion) {
3931
+ this.logger.warn(`Exports index entry ownership changed under a patch; refusing to write it. Expected producer '${displaySafe(requireOwner.producerStack)}' (${displaySafe(requireOwner.producerRegion)}), found ${current ? `'${displaySafe(current.producerStack)}' (${displaySafe(current.producerRegion)})` : "no entry"}.`);
3932
+ return false;
3933
+ }
3934
+ }
3798
3935
  const next = new Map(this.loadState.entries);
3799
3936
  next.set(exportName, entry);
3800
- await this.persist(next);
3937
+ return this.persist(next);
3801
3938
  }
3802
3939
  async applyRemoveStack(stackName, producerRegion) {
3803
3940
  await this.ensureLoaded();
3804
- if (this.loadState.kind !== "loaded") return;
3941
+ if (this.loadState.kind !== "loaded") return false;
3805
3942
  const next = new Map(this.loadState.entries);
3806
3943
  let changed = false;
3807
3944
  for (const [name, entry] of next) if (entry.producerStack === stackName && entry.producerRegion === producerRegion) {
3808
3945
  next.delete(name);
3809
3946
  changed = true;
3810
3947
  }
3811
- if (!changed) return;
3812
- await this.persist(next);
3948
+ if (!changed) return true;
3949
+ return this.persist(next);
3813
3950
  }
3814
3951
  async persist(entries) {
3815
- if (this.loadState.kind !== "loaded") return;
3952
+ if (this.loadState.kind !== "loaded") return false;
3816
3953
  const file = {
3817
3954
  indexVersion: 1,
3818
3955
  region: this.region,
@@ -3825,12 +3962,20 @@ var ExportIndexStore = class {
3825
3962
  etag,
3826
3963
  entries
3827
3964
  };
3965
+ return true;
3828
3966
  }
3967
+ /**
3968
+ * Run `op` under the If-Match retry budget. `true` means `op` reported the
3969
+ * index now holds what it was asked to write (a PUT, or a no-op skip whose
3970
+ * result was already there); `false` means this store abandoned the write
3971
+ * and logged the warn below. It does NOT throw on either arm — `state.json`
3972
+ * is the canonical record for the deploy path, and a caller that needs the
3973
+ * failure surfaced reads the return value (see {@link patchEntry}).
3974
+ */
3829
3975
  async runWithRetry(label, op) {
3830
3976
  let lastErr;
3831
3977
  for (let attempt = 0; attempt < this.opts.maxWriteRetries; attempt++) try {
3832
- await op();
3833
- return;
3978
+ return await op();
3834
3979
  } catch (err) {
3835
3980
  lastErr = err;
3836
3981
  if (this.isPreconditionFailed(err)) {
@@ -3840,9 +3985,10 @@ var ExportIndexStore = class {
3840
3985
  continue;
3841
3986
  }
3842
3987
  this.logger.warn(`Exports index ${label} failed (non-retryable): ${err instanceof Error ? err.message : String(err)}; continuing without index update`);
3843
- return;
3988
+ return false;
3844
3989
  }
3845
3990
  this.logger.warn(`Exports index ${label} exhausted ${this.opts.maxWriteRetries} retries due to concurrent writers; continuing without index update. Last error: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
3991
+ return false;
3846
3992
  }
3847
3993
  isNoSuchKey(err) {
3848
3994
  if (!err || typeof err !== "object") return false;
@@ -4173,7 +4319,8 @@ var IAMPolicyProvider = class {
4173
4319
  };
4174
4320
  }
4175
4321
  const onNotFound = async (target) => {
4176
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, `${physicalId} (${target})`);
4322
+ const clientRegion = await this.iamClient.config.region();
4323
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, `${physicalId} (${target})`);
4177
4324
  };
4178
4325
  try {
4179
4326
  if (legacyRoleFromPhysicalId) {
@@ -4493,7 +4640,8 @@ var IAMManagedPolicyProvider = class {
4493
4640
  await this.iamClient.send(new GetPolicyCommand({ PolicyArn: physicalId }));
4494
4641
  } catch (error) {
4495
4642
  if (error instanceof NoSuchEntityException) {
4496
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
4643
+ const clientRegion = await this.iamClient.config.region();
4644
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
4497
4645
  this.logger.debug(`Managed policy ${physicalId} does not exist, skipping deletion`);
4498
4646
  return;
4499
4647
  }
@@ -4970,7 +5118,8 @@ var IAMInstanceProfileProvider = class {
4970
5118
  roles = (await this.iamClient.send(new GetInstanceProfileCommand({ InstanceProfileName: physicalId }))).InstanceProfile?.Roles?.map((r) => r.RoleName).filter((name) => !!name) || [];
4971
5119
  } catch (error) {
4972
5120
  if (error instanceof NoSuchEntityException) {
4973
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
5121
+ const clientRegion = await this.iamClient.config.region();
5122
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
4974
5123
  this.logger.debug(`Instance profile ${physicalId} does not exist, skipping deletion`);
4975
5124
  return;
4976
5125
  }
@@ -4989,7 +5138,8 @@ var IAMInstanceProfileProvider = class {
4989
5138
  this.logger.debug(`Successfully deleted IAM instance profile ${logicalId}`);
4990
5139
  } catch (error) {
4991
5140
  if (error instanceof NoSuchEntityException) {
4992
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
5141
+ const clientRegion = await this.iamClient.config.region();
5142
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
4993
5143
  this.logger.debug(`Instance profile ${physicalId} does not exist, skipping deletion`);
4994
5144
  return;
4995
5145
  }
@@ -5390,7 +5540,8 @@ var IAMAccessKeyProvider = class {
5390
5540
  this.logger.debug(`Successfully deleted IAM access key ${logicalId}`);
5391
5541
  } catch (error) {
5392
5542
  if (error instanceof NoSuchEntityException) {
5393
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
5543
+ const clientRegion = await this.iamClient.config.region();
5544
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
5394
5545
  this.logger.debug(`Access key ${physicalId} does not exist, skipping deletion`);
5395
5546
  return;
5396
5547
  }
@@ -5719,7 +5870,8 @@ var IAMUserGroupProvider = class {
5719
5870
  await this.iamClient.send(new GetUserCommand({ UserName: physicalId }));
5720
5871
  } catch (error) {
5721
5872
  if (error instanceof NoSuchEntityException) {
5722
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
5873
+ const clientRegion = await this.iamClient.config.region();
5874
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
5723
5875
  this.logger.debug(`User ${physicalId} does not exist, skipping deletion`);
5724
5876
  return;
5725
5877
  }
@@ -5983,7 +6135,8 @@ var IAMUserGroupProvider = class {
5983
6135
  this.logger.debug(`Successfully deleted IAM group ${logicalId}`);
5984
6136
  } catch (error) {
5985
6137
  if (error instanceof NoSuchEntityException) {
5986
- assertRegionMatch(await this.iamClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
6138
+ const clientRegion = await this.iamClient.config.region();
6139
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
5987
6140
  this.logger.debug(`Group ${physicalId} does not exist, skipping deletion`);
5988
6141
  return;
5989
6142
  }
@@ -6829,7 +6982,8 @@ const EVENTBRIDGE_DEFAULT_ENABLED = true;
6829
6982
  * correct once every declared-but-unusable value has been refused.
6830
6983
  */
6831
6984
  function eventBridgeEnabledOf(eb) {
6832
- return coerceCfnBoolean(isPlainObject$1(eb) ? eb["EventBridgeEnabled"] : void 0) ?? EVENTBRIDGE_DEFAULT_ENABLED;
6985
+ const declared = isPlainObject$1(eb) ? eb["EventBridgeEnabled"] : void 0;
6986
+ return coerceCfnBoolean(declared) ?? EVENTBRIDGE_DEFAULT_ENABLED;
6833
6987
  }
6834
6988
  /** The notification families whose empty declaration is never SENT (issue #1759). */
6835
6989
  const NOTIFICATION_LIST_KEYS = [
@@ -9424,15 +9578,18 @@ var S3BucketProvider = class S3BucketProvider {
9424
9578
  };
9425
9579
  const replayOnUnusable = replayWarn(this.logger, context).onUnusable;
9426
9580
  const notifConfig = properties["NotificationConfiguration"];
9427
- if (notifConfig) if (!await this.applyNotificationConfiguration(bucketName, notifConfig, replayOnUnusable)) overrides.set("NotificationConfiguration", void 0);
9428
- else recordEffectiveFold("NotificationConfiguration", effectiveNotificationConfiguration(notifConfig));
9581
+ if (notifConfig) {
9582
+ if (!await this.applyNotificationConfiguration(bucketName, notifConfig, replayOnUnusable)) overrides.set("NotificationConfiguration", void 0);
9583
+ else recordEffectiveFold("NotificationConfiguration", effectiveNotificationConfiguration(notifConfig));
9584
+ }
9429
9585
  const corsConfig = properties["CorsConfiguration"];
9430
9586
  if (corsConfig?.CorsRules && Array.isArray(corsConfig.CorsRules) && corsConfig.CorsRules.length > 0) await this.applyCorsConfiguration(bucketName, corsConfig);
9431
9587
  else if (corsConfig != null) this.announceCreateEmptyCollectionSkip("CorsConfiguration", "CorsConfiguration.CorsRules");
9432
9588
  const lifecycleConfig = properties["LifecycleConfiguration"];
9433
- if (lifecycleConfig?.Rules && Array.isArray(lifecycleConfig.Rules) && lifecycleConfig.Rules.length > 0) if (!await this.applyLifecycleConfiguration(bucketName, lifecycleConfig, replayOnUnusable)) overrides.set("LifecycleConfiguration", void 0);
9434
- else recordEffectiveFold("LifecycleConfiguration", effectiveLifecycleConfiguration(lifecycleConfig));
9435
- else if (lifecycleConfig != null) this.announceCreateEmptyCollectionSkip("LifecycleConfiguration", "LifecycleConfiguration.Rules");
9589
+ if (lifecycleConfig?.Rules && Array.isArray(lifecycleConfig.Rules) && lifecycleConfig.Rules.length > 0) {
9590
+ if (!await this.applyLifecycleConfiguration(bucketName, lifecycleConfig, replayOnUnusable)) overrides.set("LifecycleConfiguration", void 0);
9591
+ else recordEffectiveFold("LifecycleConfiguration", effectiveLifecycleConfiguration(lifecycleConfig));
9592
+ } else if (lifecycleConfig != null) this.announceCreateEmptyCollectionSkip("LifecycleConfiguration", "LifecycleConfiguration.Rules");
9436
9593
  const loggingConfig = properties["LoggingConfiguration"];
9437
9594
  const loggingRefusal = replayOnUnusable ? configStringRefusal(loggingConfig, "DestinationBucketName", "", "AWS::S3::Bucket LoggingConfiguration") : void 0;
9438
9595
  if (loggingRefusal !== void 0) {
@@ -9839,7 +9996,8 @@ var S3BucketProvider = class S3BucketProvider {
9839
9996
  await this.deleteBucketWithEmptyRetry(logicalId, physicalId, allowAutoEmpty);
9840
9997
  } catch (error) {
9841
9998
  if (error instanceof NoSuchBucket) {
9842
- assertRegionMatch(await this.s3Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
9999
+ const clientRegion = await this.s3Client.config.region();
10000
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
9843
10001
  this.logger.debug(`Bucket ${physicalId} does not exist, skipping deletion`);
9844
10002
  return;
9845
10003
  }
@@ -10081,7 +10239,8 @@ var S3BucketProvider = class S3BucketProvider {
10081
10239
  }
10082
10240
  async readTags(bucket) {
10083
10241
  try {
10084
- return normalizeAwsTagsToCfn((await this.s3Client.send(new GetBucketTaggingCommand({ Bucket: bucket }))).TagSet);
10242
+ const resp = await this.s3Client.send(new GetBucketTaggingCommand({ Bucket: bucket }));
10243
+ return normalizeAwsTagsToCfn(resp.TagSet);
10085
10244
  } catch (err) {
10086
10245
  if (err.name === "NoSuchTagSet") return [];
10087
10246
  throw err;
@@ -10773,12 +10932,14 @@ var S3BucketPolicyProvider = class {
10773
10932
  this.logger.debug(`Successfully deleted S3 bucket policy ${logicalId}`);
10774
10933
  } catch (error) {
10775
10934
  if (error instanceof NoSuchBucket) {
10776
- assertRegionMatch(await this.s3Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
10935
+ const clientRegion = await this.s3Client.config.region();
10936
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
10777
10937
  this.logger.debug(`Bucket ${physicalId} does not exist, skipping policy deletion`);
10778
10938
  return;
10779
10939
  }
10780
10940
  if (error instanceof Error && (error.name === "NoSuchBucketPolicy" || error.message.includes("does not have"))) {
10781
- assertRegionMatch(await this.s3Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
10941
+ const clientRegion = await this.s3Client.config.region();
10942
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
10782
10943
  this.logger.debug(`Bucket policy for ${physicalId} does not exist, skipping`);
10783
10944
  return;
10784
10945
  }
@@ -11252,7 +11413,8 @@ var SQSQueueProvider = class {
11252
11413
  this.logger.debug(`Successfully deleted SQS queue ${logicalId}`);
11253
11414
  } catch (error) {
11254
11415
  if (error instanceof QueueDoesNotExist) {
11255
- assertRegionMatch(await this.sqsClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
11416
+ const clientRegion = await this.sqsClient.config.region();
11417
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
11256
11418
  this.logger.debug(`SQS queue ${physicalId} does not exist, skipping deletion`);
11257
11419
  return;
11258
11420
  }
@@ -11416,7 +11578,8 @@ var SQSQueueProvider = class {
11416
11578
  result["RedriveAllowPolicy"] = attributes["RedriveAllowPolicy"];
11417
11579
  }
11418
11580
  try {
11419
- result["Tags"] = normalizeAwsTagsToCfn((await this.sqsClient.send(new ListQueueTagsCommand({ QueueUrl: physicalId }))).Tags);
11581
+ const tagsResp = await this.sqsClient.send(new ListQueueTagsCommand({ QueueUrl: physicalId }));
11582
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
11420
11583
  } catch (err) {
11421
11584
  if (err instanceof QueueDoesNotExist) return void 0;
11422
11585
  throw err;
@@ -11548,7 +11711,8 @@ var SQSQueuePolicyProvider = class {
11548
11711
  this.logger.debug(`Successfully deleted SQS queue policy ${logicalId}`);
11549
11712
  } catch (error) {
11550
11713
  if (error instanceof Error && (error.name === "QueueDoesNotExist" || error.message.includes("does not exist"))) {
11551
- assertRegionMatch(await this.sqsClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
11714
+ const clientRegion = await this.sqsClient.config.region();
11715
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
11552
11716
  this.logger.debug(`Queue ${physicalId} does not exist, skipping policy deletion`);
11553
11717
  return;
11554
11718
  }
@@ -11997,7 +12161,8 @@ var SNSTopicProvider = class {
11997
12161
  this.logger.debug(`Successfully deleted SNS topic ${logicalId}`);
11998
12162
  } catch (error) {
11999
12163
  if (error instanceof NotFoundException) {
12000
- assertRegionMatch(await this.snsClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
12164
+ const clientRegion = await this.snsClient.config.region();
12165
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
12001
12166
  this.logger.debug(`SNS topic ${physicalId} does not exist, skipping deletion`);
12002
12167
  return;
12003
12168
  }
@@ -12098,7 +12263,8 @@ var SNSTopicProvider = class {
12098
12263
  }
12099
12264
  result["DeliveryStatusLogging"] = mapDeliveryStatusLogging(attrs, stateProtocolCaseMap(properties?.["DeliveryStatusLogging"]));
12100
12265
  try {
12101
- result["Tags"] = normalizeAwsTagsToCfn((await this.snsClient.send(new ListTagsForResourceCommand({ ResourceArn: physicalId }))).Tags);
12266
+ const tagsResp = await this.snsClient.send(new ListTagsForResourceCommand({ ResourceArn: physicalId }));
12267
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
12102
12268
  } catch (err) {
12103
12269
  if (err instanceof NotFoundException) return void 0;
12104
12270
  throw err;
@@ -12628,7 +12794,8 @@ var SNSSubscriptionProvider = class {
12628
12794
  this.logger.debug(`Successfully deleted SNS subscription ${logicalId}`);
12629
12795
  } catch (error) {
12630
12796
  if (error instanceof NotFoundException) {
12631
- assertRegionMatch(await this.snsClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
12797
+ const clientRegion = await this.snsClient.config.region();
12798
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
12632
12799
  this.logger.debug(`Subscription ${physicalId} does not exist, skipping deletion`);
12633
12800
  return;
12634
12801
  }
@@ -12820,7 +12987,8 @@ var SNSTopicPolicyProvider = class {
12820
12987
  this.logger.debug(`Removed policy from topic ${topicArn}`);
12821
12988
  } catch (error) {
12822
12989
  if (error instanceof Error && (error.name === "NotFoundException" || error.name === "NotFound" || error.message.includes("not found") || error.message.includes("does not exist") || error.message.includes("Invalid parameter"))) {
12823
- assertRegionMatch(await getAwsClients().sns.config.region(), context?.expectedRegion, resourceType, logicalId, topicArn);
12990
+ const clientRegion = await getAwsClients().sns.config.region();
12991
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, topicArn);
12824
12992
  this.logger.debug(`Topic ${topicArn} not found or policy already removed, skipping`);
12825
12993
  continue;
12826
12994
  }
@@ -13047,12 +13215,12 @@ var LambdaFunctionProvider = class {
13047
13215
  */
13048
13216
  unhandledByDesign = /* @__PURE__ */ new Map([["AWS::Lambda::Function", /* @__PURE__ */ new Map([["PublishToLatestPublished", "CloudFormation-only version-publishing directive with no AWS SDK equivalent"]])]]);
13049
13217
  cleanupDeleteRetryDelayMs = 2e3;
13050
- eniWaitTimeoutMs = 600 * 1e3;
13218
+ eniWaitTimeoutMs = 6e5;
13051
13219
  eniWaitInitialDelayMs = 1e4;
13052
13220
  eniWaitMaxDelayMs = 1e4;
13053
13221
  functionUpdateMaxWaitSeconds = 600;
13054
13222
  eniInitialSleepMs = 1e4;
13055
- eniDeleteRetryBudgetMs = 1800 * 1e3;
13223
+ eniDeleteRetryBudgetMs = 18e5;
13056
13224
  eniDeleteRetryIntervalMs = 15e3;
13057
13225
  constructor() {
13058
13226
  const awsClients = getAwsClients();
@@ -13189,7 +13357,7 @@ var LambdaFunctionProvider = class {
13189
13357
  const deleteMessage = deleteError instanceof Error ? deleteError.message : String(deleteError);
13190
13358
  cleanupFailure = deleteMessage;
13191
13359
  if (!/currently in the following state|ResourceConflict/i.test(deleteMessage)) break;
13192
- if (attempt < LAMBDA_CLEANUP_DELETE_MAX_ATTEMPTS - 1) await new Promise((resolve) => setTimeout(resolve, this.cleanupDeleteRetryDelayMs * (attempt + 1)));
13360
+ if (attempt < 2) await new Promise((resolve) => setTimeout(resolve, this.cleanupDeleteRetryDelayMs * (attempt + 1)));
13193
13361
  }
13194
13362
  if (cleanupFailure !== void 0) this.logger.error(`Cleanup DeleteFunction failed for ${ctx.logicalId} after ${ctx.apiName} failure — function may be orphaned: ${cleanupFailure}`);
13195
13363
  throw new ProvisioningError(`Failed to set ${ctx.propertyName} on Lambda function ${ctx.logicalId} (function was deleted to maintain atomicity): ${message}`, ctx.resourceType, ctx.logicalId, ctx.functionName, error instanceof Error ? error : void 0);
@@ -13306,15 +13474,17 @@ var LambdaFunctionProvider = class {
13306
13474
  this.logger.debug(`Updated RecursiveLoop for Lambda function ${physicalId} to '${newRecursiveLoop}'`);
13307
13475
  }
13308
13476
  const newReservedConcurrentExecutions = properties["ReservedConcurrentExecutions"];
13309
- if (newReservedConcurrentExecutions !== previousProperties["ReservedConcurrentExecutions"]) if (newReservedConcurrentExecutions === void 0) {
13310
- await this.lambdaClient.send(new DeleteFunctionConcurrencyCommand({ FunctionName: physicalId }));
13311
- this.logger.debug(`Cleared ReservedConcurrentExecutions for Lambda function ${physicalId} (template removed the property)`);
13312
- } else {
13313
- await this.lambdaClient.send(new PutFunctionConcurrencyCommand({
13314
- FunctionName: physicalId,
13315
- ReservedConcurrentExecutions: newReservedConcurrentExecutions
13316
- }));
13317
- this.logger.debug(`Updated ReservedConcurrentExecutions for Lambda function ${physicalId} to ${newReservedConcurrentExecutions}`);
13477
+ if (newReservedConcurrentExecutions !== previousProperties["ReservedConcurrentExecutions"]) {
13478
+ if (newReservedConcurrentExecutions === void 0) {
13479
+ await this.lambdaClient.send(new DeleteFunctionConcurrencyCommand({ FunctionName: physicalId }));
13480
+ this.logger.debug(`Cleared ReservedConcurrentExecutions for Lambda function ${physicalId} (template removed the property)`);
13481
+ } else {
13482
+ await this.lambdaClient.send(new PutFunctionConcurrencyCommand({
13483
+ FunctionName: physicalId,
13484
+ ReservedConcurrentExecutions: newReservedConcurrentExecutions
13485
+ }));
13486
+ this.logger.debug(`Updated ReservedConcurrentExecutions for Lambda function ${physicalId} to ${newReservedConcurrentExecutions}`);
13487
+ }
13318
13488
  }
13319
13489
  if (codeSigningChanged && newCodeSigningConfigArn !== void 0) {
13320
13490
  await this.lambdaClient.send(new PutFunctionCodeSigningConfigCommand({
@@ -13373,7 +13543,8 @@ var LambdaFunctionProvider = class {
13373
13543
  this.logger.debug(`Detached VPC config from Lambda ${physicalId} before deletion`);
13374
13544
  } catch (error) {
13375
13545
  if (error instanceof ResourceNotFoundException) {
13376
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
13546
+ const clientRegion = await this.lambdaClient.config.region();
13547
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
13377
13548
  return;
13378
13549
  }
13379
13550
  this.logger.warn(`Pre-delete VPC detach failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)} — continuing with delete`);
@@ -13385,7 +13556,8 @@ var LambdaFunctionProvider = class {
13385
13556
  this.logger.debug(`Successfully deleted Lambda function ${logicalId}`);
13386
13557
  } catch (error) {
13387
13558
  if (error instanceof ResourceNotFoundException) {
13388
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
13559
+ const clientRegion = await this.lambdaClient.config.region();
13560
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
13389
13561
  this.logger.debug(`Lambda function ${physicalId} does not exist, skipping deletion`);
13390
13562
  return;
13391
13563
  }
@@ -14119,7 +14291,8 @@ var LambdaPermissionProvider = class {
14119
14291
  this.logger.debug(`Successfully deleted Lambda permission ${logicalId}`);
14120
14292
  } catch (error) {
14121
14293
  if (error instanceof ResourceNotFoundException) {
14122
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
14294
+ const clientRegion = await this.lambdaClient.config.region();
14295
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
14123
14296
  this.logger.debug(`Lambda permission ${physicalId} does not exist, skipping deletion`);
14124
14297
  return;
14125
14298
  }
@@ -14345,7 +14518,8 @@ var LambdaUrlProvider = class {
14345
14518
  this.logger.debug(`Successfully deleted Lambda URL ${logicalId}`);
14346
14519
  } catch (error) {
14347
14520
  if (error instanceof ResourceNotFoundException) {
14348
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
14521
+ const clientRegion = await this.lambdaClient.config.region();
14522
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
14349
14523
  this.logger.debug(`Lambda URL ${physicalId} does not exist, skipping deletion`);
14350
14524
  return;
14351
14525
  }
@@ -14839,7 +15013,8 @@ var LambdaEventSourceMappingProvider = class {
14839
15013
  await this.lambdaClient.send(new GetEventSourceMappingCommand({ UUID: physicalId }));
14840
15014
  } catch (error) {
14841
15015
  if (error instanceof ResourceNotFoundException) {
14842
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
15016
+ const clientRegion = await this.lambdaClient.config.region();
15017
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
14843
15018
  this.logger.debug(`Event source mapping ${physicalId} does not exist, skipping deletion`);
14844
15019
  return;
14845
15020
  }
@@ -14849,7 +15024,8 @@ var LambdaEventSourceMappingProvider = class {
14849
15024
  this.logger.debug(`Successfully deleted event source mapping ${logicalId}`);
14850
15025
  } catch (error) {
14851
15026
  if (error instanceof ResourceNotFoundException) {
14852
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
15027
+ const clientRegion = await this.lambdaClient.config.region();
15028
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
14853
15029
  this.logger.debug(`Event source mapping ${physicalId} does not exist, skipping deletion`);
14854
15030
  return;
14855
15031
  }
@@ -15112,7 +15288,8 @@ var LambdaLayerVersionProvider = class {
15112
15288
  this.logger.debug(`Successfully deleted Lambda layer version ${logicalId}`);
15113
15289
  } catch (error) {
15114
15290
  if (error instanceof ResourceNotFoundException) {
15115
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
15291
+ const clientRegion = await this.lambdaClient.config.region();
15292
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
15116
15293
  this.logger.debug(`Lambda layer version ${physicalId} does not exist, skipping deletion`);
15117
15294
  return;
15118
15295
  }
@@ -15312,8 +15489,10 @@ function compositeIdSeparatorRefusal(resourceType, logicalId, segments, maskSecr
15312
15489
  */
15313
15490
  function packCompositeId(resourceType, logicalId, segments, options) {
15314
15491
  const refusal = compositeIdSeparatorRefusal(resourceType, logicalId, segments, options?.maskSecrets);
15315
- if (refusal !== void 0) if (options?.onRefusal) options.onRefusal(`${refusal} Continuing with the ambiguous id because the value comes from a cdkd state record, which no template edit can repair; the same value is REFUSED on a template-path create.`);
15316
- else throw new ProvisioningError(refusal, resourceType, logicalId);
15492
+ if (refusal !== void 0) {
15493
+ if (options?.onRefusal) options.onRefusal(`${refusal} Continuing with the ambiguous id because the value comes from a cdkd state record, which no template edit can repair; the same value is REFUSED on a template-path create.`);
15494
+ else throw new ProvisioningError(refusal, resourceType, logicalId);
15495
+ }
15317
15496
  return segments.map((segment) => String(segment.value)).join("|");
15318
15497
  }
15319
15498
  /** Render the expected id shape, e.g. `<databaseName>|<tableName>`. */
@@ -15556,7 +15735,8 @@ var LambdaEventInvokeConfigProvider = class {
15556
15735
  this.logger.debug(`Successfully deleted Lambda EventInvokeConfig ${logicalId}`);
15557
15736
  } catch (error) {
15558
15737
  if (error instanceof ResourceNotFoundException) {
15559
- assertRegionMatch(await this.lambdaClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
15738
+ const clientRegion = await this.lambdaClient.config.region();
15739
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
15560
15740
  this.logger.debug(`Lambda EventInvokeConfig ${physicalId} does not exist, skipping deletion`);
15561
15741
  return;
15562
15742
  }
@@ -15799,7 +15979,8 @@ var LambdaMicrovmImageProvider = class {
15799
15979
  await this.client.send(new DeleteMicrovmImageCommand({ imageIdentifier: physicalId }));
15800
15980
  } catch (error) {
15801
15981
  if (error instanceof ResourceNotFoundException$8) {
15802
- assertRegionMatch(await this.client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
15982
+ const clientRegion = await this.client.config.region();
15983
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
15803
15984
  this.logger.debug(`MicroVM image ${physicalId} does not exist, skipping deletion`);
15804
15985
  return;
15805
15986
  }
@@ -15874,7 +16055,8 @@ var LambdaMicrovmImageProvider = class {
15874
16055
  }
15875
16056
  const result = {};
15876
16057
  if (name !== void 0) result["Name"] = name;
15877
- result["Tags"] = normalizeAwsTagsToCfn((await this.client.send(new ListTagsCommand$1({ Resource: physicalId }))).Tags);
16058
+ const tagsResp = await this.client.send(new ListTagsCommand$1({ Resource: physicalId }));
16059
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
15878
16060
  return result;
15879
16061
  }
15880
16062
  /**
@@ -16726,7 +16908,7 @@ const DYNAMODB_DELETE_POLL_COST_MS = INDEX_SETTLE_POLL_INTERVAL_MS + 200;
16726
16908
  * having spent different amounts; this is the ceiling on the total, and a total
16727
16909
  * does not need to know which terms produced it.
16728
16910
  */
16729
- const DYNAMODB_DELETE_BUDGET_MS = 26 * 6e4;
16911
+ const DYNAMODB_DELETE_BUDGET_MS = 156e4;
16730
16912
  /**
16731
16913
  * What both DynamoDB providers self-report through `getMinResourceTimeoutMs()`.
16732
16914
  *
@@ -16911,7 +17093,7 @@ function autoScalingRetriesWithinDeleteBudget(budget, defaultMaxRetries) {
16911
17093
  * to ~6 min of retry backoff, which at ~8s a retry is ~45 retries spread across
16912
17094
  * however many calls the table's index and replica count produce.
16913
17095
  */
16914
- const AUTO_SCALING_TEARDOWN_RESERVE_MS = 20 * 6e4;
17096
+ const AUTO_SCALING_TEARDOWN_RESERVE_MS = 12e5;
16915
17097
  /** `withRetry`'s capped backoff step (`maxDelayMs`), the cost of one retry. */
16916
17098
  const AUTO_SCALING_RETRY_STEP_MS = 8e3;
16917
17099
  /**
@@ -17882,7 +18064,10 @@ var DynamoDBTableProvider = class {
17882
18064
  } });
17883
18065
  const billingMode = billingModeUnusable ? prevBillingMode : requestedBillingMode;
17884
18066
  const billingOrThroughputChanged = billingMode !== prevBillingMode || JSON.stringify(properties["ProvisionedThroughput"]) !== JSON.stringify(previousProperties["ProvisionedThroughput"]);
17885
- if (billingOrThroughputChanged && billingMode === "PROVISIONED" && (prevBillingMode === "PAY_PER_REQUEST" || liveBillingMode === "PAY_PER_REQUEST") && properties["OnDemandThroughput"] !== void 0) throw new ProvisioningError(`AWS::DynamoDB::Table ${logicalId}: the template flips BillingMode to PROVISIONED${properties["BillingMode"] === void 0 ? " (BillingMode is absent from the template, which CloudFormation resets to PROVISIONED)" : ""} while still declaring OnDemandThroughput, which AWS accepts only on a PAY_PER_REQUEST table. Nothing was applied. Remove OnDemandThroughput, or keep BillingMode: PAY_PER_REQUEST.`, logicalId, resourceType);
18067
+ if (billingOrThroughputChanged && billingMode === "PROVISIONED" && (prevBillingMode === "PAY_PER_REQUEST" || liveBillingMode === "PAY_PER_REQUEST") && properties["OnDemandThroughput"] !== void 0) {
18068
+ const absentNote = properties["BillingMode"] === void 0 ? " (BillingMode is absent from the template, which CloudFormation resets to PROVISIONED)" : "";
18069
+ throw new ProvisioningError(`AWS::DynamoDB::Table ${logicalId}: the template flips BillingMode to PROVISIONED${absentNote} while still declaring OnDemandThroughput, which AWS accepts only on a PAY_PER_REQUEST table. Nothing was applied. Remove OnDemandThroughput, or keep BillingMode: PAY_PER_REQUEST.`, logicalId, resourceType);
18070
+ }
17886
18071
  const liveIndexes = table?.GlobalSecondaryIndexes ?? [];
17887
18072
  const desiredIndexByName = /* @__PURE__ */ new Map();
17888
18073
  for (const entry of Array.isArray(properties["GlobalSecondaryIndexes"]) ? properties["GlobalSecondaryIndexes"] : []) {
@@ -18172,7 +18357,8 @@ var DynamoDBTableProvider = class {
18172
18357
  this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
18173
18358
  } catch (error) {
18174
18359
  if (error instanceof ResourceNotFoundException$1) {
18175
- assertRegionMatch(await this.dynamoDBClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
18360
+ const clientRegion = await this.dynamoDBClient.config.region();
18361
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
18176
18362
  this.logger.debug(`DynamoDB table ${physicalId} does not exist, skipping deletion`);
18177
18363
  this.deleteBudgets.release(deleteBudgetKey(physicalId, context?.expectedRegion));
18178
18364
  this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
@@ -19146,7 +19332,8 @@ var DynamoDBTableProvider = class {
19146
19332
  if (table.DeletionProtectionEnabled !== void 0) result["DeletionProtectionEnabled"] = table.DeletionProtectionEnabled;
19147
19333
  if (table.TableClassSummary?.TableClass) result["TableClass"] = table.TableClassSummary.TableClass;
19148
19334
  if (table.TableArn) try {
19149
- result["Tags"] = normalizeAwsTagsToCfn((await this.dynamoDBClient.send(new ListTagsOfResourceCommand({ ResourceArn: table.TableArn }))).Tags);
19335
+ const tagsResp = await this.dynamoDBClient.send(new ListTagsOfResourceCommand({ ResourceArn: table.TableArn }));
19336
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
19150
19337
  } catch (err) {
19151
19338
  if (err instanceof ResourceNotFoundException$1) return void 0;
19152
19339
  throw err;
@@ -20219,10 +20406,14 @@ var DynamoDBGlobalTableProvider = class {
20219
20406
  const onDemand = { ...gsi.OnDemandThroughput };
20220
20407
  const unresolvedMembers = [];
20221
20408
  if (!billingFlipped && previousOnDemand) {
20222
- if (previousOnDemand.MaxReadRequestUnits !== void 0 && onDemand.MaxReadRequestUnits === void 0) if (declared?.readDeclared) unresolvedMembers.push("MaxReadRequestUnits");
20223
- else onDemand.MaxReadRequestUnits = ON_DEMAND_LIMIT_RESET;
20224
- if (previousOnDemand.MaxWriteRequestUnits !== void 0 && onDemand.MaxWriteRequestUnits === void 0) if (declared?.writeDeclared) unresolvedMembers.push("MaxWriteRequestUnits");
20225
- else onDemand.MaxWriteRequestUnits = ON_DEMAND_LIMIT_RESET;
20409
+ if (previousOnDemand.MaxReadRequestUnits !== void 0 && onDemand.MaxReadRequestUnits === void 0) {
20410
+ if (declared?.readDeclared) unresolvedMembers.push("MaxReadRequestUnits");
20411
+ else onDemand.MaxReadRequestUnits = ON_DEMAND_LIMIT_RESET;
20412
+ }
20413
+ if (previousOnDemand.MaxWriteRequestUnits !== void 0 && onDemand.MaxWriteRequestUnits === void 0) {
20414
+ if (declared?.writeDeclared) unresolvedMembers.push("MaxWriteRequestUnits");
20415
+ else onDemand.MaxWriteRequestUnits = ON_DEMAND_LIMIT_RESET;
20416
+ }
20226
20417
  }
20227
20418
  if (Object.keys(onDemand).length > 0) update.OnDemandThroughput = onDemand;
20228
20419
  if (!billingFlipped && gsi.ProvisionedThroughput) update.ProvisionedThroughput = gsi.ProvisionedThroughput;
@@ -20811,7 +21002,8 @@ var DynamoDBGlobalTableProvider = class {
20811
21002
  this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
20812
21003
  } catch (error) {
20813
21004
  if (error instanceof ResourceNotFoundException$1) {
20814
- assertRegionMatch(await this.dynamoDBClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
21005
+ const clientRegion = await this.dynamoDBClient.config.region();
21006
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
20815
21007
  this.logger.debug(`DynamoDB GlobalTable ${physicalId} does not exist, skipping`);
20816
21008
  this.deleteBudgets.release(deleteBudgetKey(physicalId, context?.expectedRegion));
20817
21009
  this.protectionFlips.release(deleteBudgetKey(physicalId, context?.expectedRegion));
@@ -20922,7 +21114,8 @@ var DynamoDBGlobalTableProvider = class {
20922
21114
  const subs = await this.readReplicaSubSpecs(client, tableNameForSubs, regionLabel);
20923
21115
  Object.assign(entry, subs);
20924
21116
  if (replicaArn) try {
20925
- entry["Tags"] = normalizeAwsTagsToCfn((await client.send(new ListTagsOfResourceCommand({ ResourceArn: replicaArn }))).Tags);
21117
+ const tagsResp = await client.send(new ListTagsOfResourceCommand({ ResourceArn: replicaArn }));
21118
+ entry["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
20926
21119
  } catch (tagErr) {
20927
21120
  if (tagErr instanceof ResourceNotFoundException$1) {
20928
21121
  if (isLocal) throw tagErr;
@@ -23152,7 +23345,8 @@ var LogsLogGroupProvider = class {
23152
23345
  const nextClass = normalizeClass(properties["LogGroupClass"]);
23153
23346
  if (prevClass !== nextClass) {
23154
23347
  const replaceFlags = "--replace --force-stateful-recreation";
23155
- throw new ResourceUpdateNotSupportedError("AWS::Logs::LogGroup", logicalId, `the LogGroupClass ('${prevClass}' -> '${nextClass}') cannot be changed after creation. ${isTruthyCfnBoolean(previousProperties["DeletionProtectionEnabled"]) ? `cdkd's recorded properties for this log group carry DeletionProtectionEnabled, so ${replaceFlags} alone will NOT succeed while AWS still has it on: the replacement normally deletes the log group, AWS refuses that delete while protection is on, and cdkd deploy has no --remove-protection flag to clear it (only cdkd destroy and cdkd state destroy act on one). Read "Deletion protection blocks a replacement" in docs/cli-deploy-safety.md BEFORE you disable anything: whether disabling helps at all, and what the flag ends up as, depend on your UpdateReplacePolicy and on whether the deploy completes — neither of which this refusal can see. Then disable deletion protection — \`aws logs put-log-group-deletion-protection --log-group-identifier '${physicalId}' --no-deletion-protection-enabled\`, or via the console — and re-deploy with ${replaceFlags} to delete + recreate the log group under the new class (its stored log events are lost). Setting DeletionProtectionEnabled: false in the template does NOT clear it in the same deploy: this refusal fires before that property is applied, so that route needs its own deploy with the LogGroupClass change reverted. Or revert the LogGroupClass change and keep the current class.` : `Re-deploy with ${replaceFlags} to delete + recreate the log group under the new class (its stored log events are lost), or revert the LogGroupClass change.`} Note --force-stateful-recreation has NO per-resource granularity: it clears the data guard for every replacement target in the run`);
23348
+ const remedy = isTruthyCfnBoolean(previousProperties["DeletionProtectionEnabled"]) ? `cdkd's recorded properties for this log group carry DeletionProtectionEnabled, so ${replaceFlags} alone will NOT succeed while AWS still has it on: the replacement normally deletes the log group, AWS refuses that delete while protection is on, and cdkd deploy has no --remove-protection flag to clear it (only cdkd destroy and cdkd state destroy act on one). Read "Deletion protection blocks a replacement" in docs/cli-deploy-safety.md BEFORE you disable anything: whether disabling helps at all, and what the flag ends up as, depend on your UpdateReplacePolicy and on whether the deploy completes — neither of which this refusal can see. Then disable deletion protection — \`aws logs put-log-group-deletion-protection --log-group-identifier '${physicalId}' --no-deletion-protection-enabled\`, or via the console — and re-deploy with ${replaceFlags} to delete + recreate the log group under the new class (its stored log events are lost). Setting DeletionProtectionEnabled: false in the template does NOT clear it in the same deploy: this refusal fires before that property is applied, so that route needs its own deploy with the LogGroupClass change reverted. Or revert the LogGroupClass change and keep the current class.` : `Re-deploy with ${replaceFlags} to delete + recreate the log group under the new class (its stored log events are lost), or revert the LogGroupClass change.`;
23349
+ throw new ResourceUpdateNotSupportedError("AWS::Logs::LogGroup", logicalId, `the LogGroupClass ('${prevClass}' -> '${nextClass}') cannot be changed after creation. ${remedy} Note --force-stateful-recreation has NO per-resource granularity: it clears the data guard for every replacement target in the run`);
23156
23350
  }
23157
23351
  const rawRetention = properties["RetentionInDays"];
23158
23352
  const rawPreviousRetention = previousProperties["RetentionInDays"];
@@ -23170,18 +23364,22 @@ var LogsLogGroupProvider = class {
23170
23364
  else await this.logsClient.send(new DisassociateKmsKeyCommand({ logGroupName: physicalId }));
23171
23365
  this.logger.debug(`Updated KMS key association for log group ${physicalId}`);
23172
23366
  }
23173
- if (retentionInDays !== oldRetentionInDays || !sameRawRetention && Boolean(rawPreviousRetention) && oldRetentionInDays === void 0) if (retentionInDays !== void 0 && retentionInDays > 0) await this.logsClient.send(new PutRetentionPolicyCommand({
23174
- logGroupName: physicalId,
23175
- retentionInDays
23176
- }));
23177
- else await this.logsClient.send(new DeleteRetentionPolicyCommand({ logGroupName: physicalId }));
23178
- if (JSON.stringify(properties["DataProtectionPolicy"]) !== JSON.stringify(previousProperties["DataProtectionPolicy"])) if (properties["DataProtectionPolicy"]) {
23179
- const policyDocument = typeof properties["DataProtectionPolicy"] === "string" ? properties["DataProtectionPolicy"] : JSON.stringify(properties["DataProtectionPolicy"]);
23180
- await this.logsClient.send(new PutDataProtectionPolicyCommand({
23181
- logGroupIdentifier: physicalId,
23182
- policyDocument
23367
+ if (retentionInDays !== oldRetentionInDays || !sameRawRetention && Boolean(rawPreviousRetention) && oldRetentionInDays === void 0) {
23368
+ if (retentionInDays !== void 0 && retentionInDays > 0) await this.logsClient.send(new PutRetentionPolicyCommand({
23369
+ logGroupName: physicalId,
23370
+ retentionInDays
23183
23371
  }));
23184
- } else await this.logsClient.send(new DeleteDataProtectionPolicyCommand({ logGroupIdentifier: physicalId }));
23372
+ else await this.logsClient.send(new DeleteRetentionPolicyCommand({ logGroupName: physicalId }));
23373
+ }
23374
+ if (JSON.stringify(properties["DataProtectionPolicy"]) !== JSON.stringify(previousProperties["DataProtectionPolicy"])) {
23375
+ if (properties["DataProtectionPolicy"]) {
23376
+ const policyDocument = typeof properties["DataProtectionPolicy"] === "string" ? properties["DataProtectionPolicy"] : JSON.stringify(properties["DataProtectionPolicy"]);
23377
+ await this.logsClient.send(new PutDataProtectionPolicyCommand({
23378
+ logGroupIdentifier: physicalId,
23379
+ policyDocument
23380
+ }));
23381
+ } else await this.logsClient.send(new DeleteDataProtectionPolicyCommand({ logGroupIdentifier: physicalId }));
23382
+ }
23185
23383
  if (properties["DeletionProtectionEnabled"] !== previousProperties["DeletionProtectionEnabled"]) {
23186
23384
  const next = properties["DeletionProtectionEnabled"];
23187
23385
  if (next !== void 0) await this.logsClient.send(new PutLogGroupDeletionProtectionCommand({
@@ -23206,18 +23404,20 @@ var LogsLogGroupProvider = class {
23206
23404
  }
23207
23405
  const newFieldIndex = properties["FieldIndexPolicies"];
23208
23406
  const oldFieldIndex = previousProperties["FieldIndexPolicies"];
23209
- if (JSON.stringify(newFieldIndex) !== JSON.stringify(oldFieldIndex)) if (newFieldIndex && newFieldIndex.length > 0) {
23210
- if (newFieldIndex.length > 1) this.logger.debug(`Log group ${physicalId} declares ${newFieldIndex.length} FieldIndexPolicies; AWS only supports one log-group-level field index policy. Applying the first.`);
23211
- const first = newFieldIndex[0];
23212
- const policyDocument = typeof first === "string" ? first : JSON.stringify(first);
23213
- await this.logsClient.send(new PutIndexPolicyCommand({
23214
- logGroupIdentifier: physicalId,
23215
- policyDocument
23216
- }));
23217
- } else try {
23218
- await this.logsClient.send(new DeleteIndexPolicyCommand({ logGroupIdentifier: physicalId }));
23219
- } catch (err) {
23220
- if (!(err instanceof ResourceNotFoundException$5)) throw err;
23407
+ if (JSON.stringify(newFieldIndex) !== JSON.stringify(oldFieldIndex)) {
23408
+ if (newFieldIndex && newFieldIndex.length > 0) {
23409
+ if (newFieldIndex.length > 1) this.logger.debug(`Log group ${physicalId} declares ${newFieldIndex.length} FieldIndexPolicies; AWS only supports one log-group-level field index policy. Applying the first.`);
23410
+ const first = newFieldIndex[0];
23411
+ const policyDocument = typeof first === "string" ? first : JSON.stringify(first);
23412
+ await this.logsClient.send(new PutIndexPolicyCommand({
23413
+ logGroupIdentifier: physicalId,
23414
+ policyDocument
23415
+ }));
23416
+ } else try {
23417
+ await this.logsClient.send(new DeleteIndexPolicyCommand({ logGroupIdentifier: physicalId }));
23418
+ } catch (err) {
23419
+ if (!(err instanceof ResourceNotFoundException$5)) throw err;
23420
+ }
23221
23421
  }
23222
23422
  const newTags = properties["Tags"];
23223
23423
  const oldTags = previousProperties["Tags"];
@@ -23264,7 +23464,8 @@ var LogsLogGroupProvider = class {
23264
23464
  this.logger.debug(`Successfully deleted log group ${logicalId}`);
23265
23465
  } catch (error) {
23266
23466
  if (error instanceof ResourceNotFoundException$5) {
23267
- assertRegionMatch(await this.logsClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
23467
+ const clientRegion = await this.logsClient.config.region();
23468
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
23268
23469
  this.logger.debug(`Log group ${physicalId} does not exist, skipping deletion`);
23269
23470
  return;
23270
23471
  }
@@ -23363,7 +23564,8 @@ var LogsLogGroupProvider = class {
23363
23564
  if (found.arn) {
23364
23565
  const arnForTags = found.arn.replace(/:\*$/, "");
23365
23566
  try {
23366
- tags = normalizeAwsTagsToCfn((await this.logsClient.send(new ListTagsForResourceCommand$5({ resourceArn: arnForTags }))).tags);
23567
+ const tagsResp = await this.logsClient.send(new ListTagsForResourceCommand$5({ resourceArn: arnForTags }));
23568
+ tags = normalizeAwsTagsToCfn(tagsResp.tags);
23367
23569
  } catch (err) {
23368
23570
  if (err instanceof ResourceNotFoundException$5) return void 0;
23369
23571
  throw err;
@@ -23527,7 +23729,8 @@ var CloudWatchAlarmProvider = class {
23527
23729
  this.logger.debug(`Successfully deleted CloudWatch alarm ${logicalId}`);
23528
23730
  } catch (error) {
23529
23731
  if (error instanceof Error && error.name === "ResourceNotFound") {
23530
- assertRegionMatch(await this.cloudWatchClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
23732
+ const clientRegion = await this.cloudWatchClient.config.region();
23733
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
23531
23734
  this.logger.debug(`Alarm ${physicalId} does not exist, skipping deletion`);
23532
23735
  return;
23533
23736
  }
@@ -23718,7 +23921,8 @@ var CloudWatchAlarmProvider = class {
23718
23921
  if (alarm.EvaluateLowSampleCountPercentile !== void 0) result["EvaluateLowSampleCountPercentile"] = alarm.EvaluateLowSampleCountPercentile;
23719
23922
  if (alarm.ThresholdMetricId !== void 0) result["ThresholdMetricId"] = alarm.ThresholdMetricId;
23720
23923
  if (alarm.AlarmArn) try {
23721
- result["Tags"] = normalizeAwsTagsToCfn((await this.cloudWatchClient.send(new ListTagsForResourceCommand$4({ ResourceARN: alarm.AlarmArn }))).Tags);
23924
+ const tagsResp = await this.cloudWatchClient.send(new ListTagsForResourceCommand$4({ ResourceARN: alarm.AlarmArn }));
23925
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
23722
23926
  } catch (err) {
23723
23927
  this.logger.debug(`CloudWatch ListTagsForResource(${alarm.AlarmArn}) failed: ${err instanceof Error ? err.message : String(err)}`);
23724
23928
  }
@@ -23856,7 +24060,8 @@ var CloudWatchAnomalyDetectorProvider = class {
23856
24060
  this.logger.debug(`Successfully deleted CloudWatch anomaly detector ${logicalId}`);
23857
24061
  } catch (error) {
23858
24062
  if (error instanceof ResourceNotFoundException$4) {
23859
- assertRegionMatch(await this.cloudWatchClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
24063
+ const clientRegion = await this.cloudWatchClient.config.region();
24064
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
23860
24065
  this.logger.debug(`Anomaly detector ${physicalId} does not exist, skipping deletion`);
23861
24066
  return;
23862
24067
  }
@@ -24136,7 +24341,8 @@ var SecretsManagerSecretProvider = class {
24136
24341
  this.logger.debug(`Successfully deleted secret ${logicalId}`);
24137
24342
  } catch (error) {
24138
24343
  if (error instanceof ResourceNotFoundException$3) {
24139
- assertRegionMatch(await this.smClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
24344
+ const clientRegion = await this.smClient.config.region();
24345
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
24140
24346
  this.logger.debug(`Secret ${physicalId} does not exist, skipping deletion`);
24141
24347
  return;
24142
24348
  }
@@ -24530,7 +24736,8 @@ var SSMParameterProvider = class {
24530
24736
  * `import()` now refuses it — and not here. Do not re-add it.
24531
24737
  */
24532
24738
  async buildParameterArnUnguarded(name, mask) {
24533
- const accountInfo = await getAccountInfo(await this.ssmClient.config.region());
24739
+ const region = await this.ssmClient.config.region();
24740
+ const accountInfo = await getAccountInfo(region);
24534
24741
  if (accountInfo.fabricated) {
24535
24742
  this.logger.warn(mask(`Cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so the Arn attribute for SSM parameter ${name} would be fabricated and is NOT recorded. An Fn::GetAtt on it will fail until a later deploy resolves the account.`));
24536
24743
  return;
@@ -24664,7 +24871,8 @@ var SSMParameterProvider = class {
24664
24871
  this.logger.debug(`Successfully deleted SSM parameter ${logicalId}`);
24665
24872
  } catch (error) {
24666
24873
  if (error instanceof ParameterNotFound) {
24667
- assertRegionMatch(await this.ssmClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
24874
+ const clientRegion = await this.ssmClient.config.region();
24875
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
24668
24876
  this.logger.debug(`Parameter ${physicalId} does not exist, skipping deletion`);
24669
24877
  return;
24670
24878
  }
@@ -24748,10 +24956,11 @@ var SSMParameterProvider = class {
24748
24956
  } catch {}
24749
24957
  if (!policiesEmitted) result["Policies"] = [];
24750
24958
  try {
24751
- const tagArr = normalizeAwsTagsToCfn((await this.ssmClient.send(new ListTagsForResourceCommand$2({
24959
+ const tagsResp = await this.ssmClient.send(new ListTagsForResourceCommand$2({
24752
24960
  ResourceType: "Parameter",
24753
24961
  ResourceId: physicalId
24754
- }))).TagList);
24962
+ }));
24963
+ const tagArr = normalizeAwsTagsToCfn(tagsResp.TagList);
24755
24964
  result["Tags"] = Object.fromEntries(tagArr.map((t) => [t.Key, t.Value]));
24756
24965
  } catch {}
24757
24966
  return result;
@@ -25113,7 +25322,8 @@ var EventBridgeRuleProvider = class {
25113
25322
  }))).Targets || []).map((t) => t.Id).filter((id) => id !== void 0);
25114
25323
  } catch (error) {
25115
25324
  if (error instanceof ResourceNotFoundException$2) {
25116
- assertRegionMatch(await this.eventBridgeClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
25325
+ const clientRegion = await this.eventBridgeClient.config.region();
25326
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
25117
25327
  this.logger.debug(`Rule ${ruleName} does not exist, skipping deletion`);
25118
25328
  return;
25119
25329
  }
@@ -25134,7 +25344,8 @@ var EventBridgeRuleProvider = class {
25134
25344
  this.logger.debug(`Successfully deleted EventBridge rule ${logicalId}`);
25135
25345
  } catch (error) {
25136
25346
  if (error instanceof ResourceNotFoundException$2) {
25137
- assertRegionMatch(await this.eventBridgeClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
25347
+ const clientRegion = await this.eventBridgeClient.config.region();
25348
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
25138
25349
  this.logger.debug(`Rule ${ruleName} does not exist, skipping deletion`);
25139
25350
  return;
25140
25351
  }
@@ -25206,7 +25417,8 @@ var EventBridgeRuleProvider = class {
25206
25417
  if (!(err instanceof ResourceNotFoundException$2)) throw err;
25207
25418
  }
25208
25419
  try {
25209
- result["Tags"] = normalizeAwsTagsToCfn((await this.eventBridgeClient.send(new ListTagsForResourceCommand$1({ ResourceARN: physicalId }))).Tags);
25420
+ const tagsResp = await this.eventBridgeClient.send(new ListTagsForResourceCommand$1({ ResourceARN: physicalId }));
25421
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
25210
25422
  } catch (err) {
25211
25423
  if (!(err instanceof ResourceNotFoundException$2)) throw err;
25212
25424
  }
@@ -25443,13 +25655,15 @@ var EventBridgeBusProvider = class {
25443
25655
  this.logger.debug(`Successfully deleted EventBus ${logicalId}`);
25444
25656
  } catch (error) {
25445
25657
  if (error instanceof ResourceNotFoundException$2) {
25446
- assertRegionMatch(await this.eventBridgeClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
25658
+ const clientRegion = await this.eventBridgeClient.config.region();
25659
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
25447
25660
  this.logger.debug(`EventBus ${physicalId} does not exist, skipping`);
25448
25661
  return;
25449
25662
  }
25450
25663
  const msg = error instanceof Error ? error.message : String(error);
25451
25664
  if (msg.includes("does not exist")) {
25452
- assertRegionMatch(await this.eventBridgeClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
25665
+ const clientRegion = await this.eventBridgeClient.config.region();
25666
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
25453
25667
  this.logger.debug(`EventBus ${physicalId} does not exist, skipping`);
25454
25668
  return;
25455
25669
  }
@@ -25534,7 +25748,8 @@ var EventBridgeBusProvider = class {
25534
25748
  result["Policy"] = resp.Policy;
25535
25749
  }
25536
25750
  if (resp.Arn) try {
25537
- result["Tags"] = normalizeAwsTagsToCfn((await this.eventBridgeClient.send(new ListTagsForResourceCommand$1({ ResourceARN: resp.Arn }))).Tags);
25751
+ const tagsResp = await this.eventBridgeClient.send(new ListTagsForResourceCommand$1({ ResourceARN: resp.Arn }));
25752
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
25538
25753
  } catch (err) {
25539
25754
  if (err instanceof ResourceNotFoundException$2) return void 0;
25540
25755
  throw err;
@@ -26297,7 +26512,8 @@ var EC2Provider = class {
26297
26512
  return;
26298
26513
  } catch (error) {
26299
26514
  if (this.isNotFoundError(error)) {
26300
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
26515
+ const clientRegion = await this.ec2Client.config.region();
26516
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26301
26517
  this.logger.debug(`VPC ${physicalId} does not exist, skipping deletion`);
26302
26518
  return;
26303
26519
  }
@@ -26470,7 +26686,8 @@ var EC2Provider = class {
26470
26686
  return;
26471
26687
  } catch (error) {
26472
26688
  if (this.isNotFoundError(error)) {
26473
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
26689
+ const clientRegion = await this.ec2Client.config.region();
26690
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26474
26691
  this.logger.debug(`Subnet ${physicalId} does not exist, skipping deletion`);
26475
26692
  return;
26476
26693
  }
@@ -26556,7 +26773,8 @@ var EC2Provider = class {
26556
26773
  this.logger.debug(`Successfully deleted InternetGateway ${logicalId}`);
26557
26774
  } catch (error) {
26558
26775
  if (this.isNotFoundError(error)) {
26559
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
26776
+ const clientRegion = await this.ec2Client.config.region();
26777
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26560
26778
  this.logger.debug(`InternetGateway ${physicalId} does not exist, skipping deletion`);
26561
26779
  return;
26562
26780
  }
@@ -26608,13 +26826,15 @@ var EC2Provider = class {
26608
26826
  publicIp = response.PublicIp;
26609
26827
  await this.applyTags(allocationId, properties, logicalId);
26610
26828
  const instanceId = properties["InstanceId"];
26611
- if (instanceId) if (process.env["CDKD_NO_WAIT"] === "true") this.logger.warn(`EIP ${logicalId} (${allocationId}) was NOT associated with ${instanceId}: --no-wait skips the instance running-state wait, and AssociateAddress rejects an instance that is not yet running. Verify and associate once the instance is running: aws ec2 describe-instances --instance-ids ${instanceId} --query 'Reservations[].Instances[].State.Name' && aws ec2 associate-address --allocation-id ${allocationId} --instance-id ${instanceId}`);
26612
- else {
26613
- await this.ec2Client.send(new AssociateAddressCommand({
26614
- AllocationId: allocationId,
26615
- InstanceId: instanceId
26616
- }));
26617
- this.logger.debug(`Associated EIP ${logicalId} (${allocationId}) to ${instanceId}`);
26829
+ if (instanceId) {
26830
+ if (process.env["CDKD_NO_WAIT"] === "true") this.logger.warn(`EIP ${logicalId} (${allocationId}) was NOT associated with ${instanceId}: --no-wait skips the instance running-state wait, and AssociateAddress rejects an instance that is not yet running. Verify and associate once the instance is running: aws ec2 describe-instances --instance-ids ${instanceId} --query 'Reservations[].Instances[].State.Name' && aws ec2 associate-address --allocation-id ${allocationId} --instance-id ${instanceId}`);
26831
+ else {
26832
+ await this.ec2Client.send(new AssociateAddressCommand({
26833
+ AllocationId: allocationId,
26834
+ InstanceId: instanceId
26835
+ }));
26836
+ this.logger.debug(`Associated EIP ${logicalId} (${allocationId}) to ${instanceId}`);
26837
+ }
26618
26838
  }
26619
26839
  this.logger.debug(`Successfully created EIP ${logicalId}: ${allocationId} (${publicIp})`);
26620
26840
  } catch (error) {
@@ -26639,19 +26859,21 @@ var EC2Provider = class {
26639
26859
  await this.applyTagDiff(allocationId, previousProperties["Tags"], properties["Tags"]);
26640
26860
  const oldInstanceId = previousProperties["InstanceId"];
26641
26861
  const newInstanceId = properties["InstanceId"];
26642
- if (oldInstanceId !== newInstanceId) if (newInstanceId) try {
26643
- await this.ec2Client.send(new AssociateAddressCommand({
26644
- AllocationId: allocationId,
26645
- InstanceId: newInstanceId,
26646
- AllowReassociation: true
26647
- }));
26648
- } catch (error) {
26649
- if (process.env["CDKD_NO_WAIT"] === "true" && this.isIncorrectInstanceStateError(error)) this.logger.warn(`EIP ${logicalId} (${allocationId}) was NOT associated with ${newInstanceId}: --no-wait skips the instance running-state wait, and AssociateAddress rejects an instance that is not yet running. Verify and associate once the instance is running: aws ec2 describe-instances --instance-ids ${newInstanceId} --query 'Reservations[].Instances[].State.Name' && aws ec2 associate-address --allocation-id ${allocationId} --instance-id ${newInstanceId} --allow-reassociation`);
26650
- else throw error;
26651
- }
26652
- else {
26653
- const associationId = (await this.ec2Client.send(new DescribeAddressesCommand({ AllocationIds: [allocationId] }))).Addresses?.[0]?.AssociationId;
26654
- if (associationId) await this.ec2Client.send(new DisassociateAddressCommand({ AssociationId: associationId }));
26862
+ if (oldInstanceId !== newInstanceId) {
26863
+ if (newInstanceId) try {
26864
+ await this.ec2Client.send(new AssociateAddressCommand({
26865
+ AllocationId: allocationId,
26866
+ InstanceId: newInstanceId,
26867
+ AllowReassociation: true
26868
+ }));
26869
+ } catch (error) {
26870
+ if (process.env["CDKD_NO_WAIT"] === "true" && this.isIncorrectInstanceStateError(error)) this.logger.warn(`EIP ${logicalId} (${allocationId}) was NOT associated with ${newInstanceId}: --no-wait skips the instance running-state wait, and AssociateAddress rejects an instance that is not yet running. Verify and associate once the instance is running: aws ec2 describe-instances --instance-ids ${newInstanceId} --query 'Reservations[].Instances[].State.Name' && aws ec2 associate-address --allocation-id ${allocationId} --instance-id ${newInstanceId} --allow-reassociation`);
26871
+ else throw error;
26872
+ }
26873
+ else {
26874
+ const associationId = (await this.ec2Client.send(new DescribeAddressesCommand({ AllocationIds: [allocationId] }))).Addresses?.[0]?.AssociationId;
26875
+ if (associationId) await this.ec2Client.send(new DisassociateAddressCommand({ AssociationId: associationId }));
26876
+ }
26655
26877
  }
26656
26878
  }
26657
26879
  return {
@@ -26675,7 +26897,8 @@ var EC2Provider = class {
26675
26897
  this.logger.debug(`Successfully deleted EIP ${logicalId}`);
26676
26898
  } catch (error) {
26677
26899
  if (this.isNotFoundError(error)) {
26678
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
26900
+ const clientRegion = await this.ec2Client.config.region();
26901
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26679
26902
  this.logger.debug(`EIP ${physicalId} does not exist, skipping deletion`);
26680
26903
  return;
26681
26904
  }
@@ -26742,7 +26965,8 @@ var EC2Provider = class {
26742
26965
  this.logger.debug(`Successfully deleted VPCGatewayAttachment ${logicalId}`);
26743
26966
  } catch (error) {
26744
26967
  if (this.isNotFoundError(error)) {
26745
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
26968
+ const clientRegion = await this.ec2Client.config.region();
26969
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26746
26970
  this.logger.debug(`VPCGatewayAttachment ${physicalId} does not exist, skipping deletion`);
26747
26971
  return;
26748
26972
  }
@@ -26800,7 +27024,8 @@ var EC2Provider = class {
26800
27024
  await this.ec2Client.send(new DeleteNatGatewayCommand({ NatGatewayId: physicalId }));
26801
27025
  } catch (error) {
26802
27026
  if (this.isNotFoundError(error)) {
26803
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
27027
+ const clientRegion = await this.ec2Client.config.region();
27028
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26804
27029
  this.logger.debug(`NatGateway ${physicalId} does not exist, skipping deletion`);
26805
27030
  return;
26806
27031
  }
@@ -26855,7 +27080,8 @@ var EC2Provider = class {
26855
27080
  this.logger.debug(`Successfully deleted RouteTable ${logicalId}`);
26856
27081
  } catch (error) {
26857
27082
  if (this.isNotFoundError(error)) {
26858
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
27083
+ const clientRegion = await this.ec2Client.config.region();
27084
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26859
27085
  this.logger.debug(`RouteTable ${physicalId} does not exist, skipping deletion`);
26860
27086
  return;
26861
27087
  }
@@ -26953,7 +27179,8 @@ var EC2Provider = class {
26953
27179
  this.logger.debug(`Successfully deleted Route ${logicalId}`);
26954
27180
  } catch (error) {
26955
27181
  if (this.isNotFoundError(error)) {
26956
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
27182
+ const clientRegion = await this.ec2Client.config.region();
27183
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26957
27184
  this.logger.debug(`Route ${physicalId} does not exist, skipping deletion`);
26958
27185
  return;
26959
27186
  }
@@ -26991,7 +27218,8 @@ var EC2Provider = class {
26991
27218
  this.logger.debug(`Successfully deleted SubnetRouteTableAssociation ${logicalId}`);
26992
27219
  } catch (error) {
26993
27220
  if (this.isNotFoundError(error)) {
26994
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
27221
+ const clientRegion = await this.ec2Client.config.region();
27222
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
26995
27223
  this.logger.debug(`SubnetRouteTableAssociation ${physicalId} does not exist, skipping deletion`);
26996
27224
  return;
26997
27225
  }
@@ -27142,7 +27370,8 @@ var EC2Provider = class {
27142
27370
  return;
27143
27371
  } catch (error) {
27144
27372
  if (this.isNotFoundError(error)) {
27145
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
27373
+ const clientRegion = await this.ec2Client.config.region();
27374
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
27146
27375
  this.logger.debug(`SecurityGroup ${physicalId} does not exist, skipping deletion`);
27147
27376
  return;
27148
27377
  }
@@ -27372,7 +27601,8 @@ var EC2Provider = class {
27372
27601
  this.logger.debug(`Successfully deleted SecurityGroupIngress ${logicalId}`);
27373
27602
  } catch (error) {
27374
27603
  if (this.isNotFoundError(error)) {
27375
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
27604
+ const clientRegion = await this.ec2Client.config.region();
27605
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
27376
27606
  this.logger.debug(`SecurityGroupIngress ${physicalId} does not exist, skipping deletion`);
27377
27607
  return;
27378
27608
  }
@@ -27431,10 +27661,12 @@ var EC2Provider = class {
27431
27661
  maxDelay: 5
27432
27662
  }, { InstanceIds: [instanceId] });
27433
27663
  } else this.logger.debug(`Instance ${instanceId} launched (skipping running-state wait per --no-wait)`);
27434
- if (iamInstanceProfile) if (process.env["CDKD_NO_WAIT"] === "true") {
27435
- const profileRef = iamInstanceProfile.arn ?? iamInstanceProfile.name;
27436
- this.logger.warn(`Skipped the IAM instance profile association check for ${logicalId} (${instanceId}) because --no-wait leaves the instance in 'pending', where AssociateIamInstanceProfile is rejected. RunInstances associates the profile asynchronously and can complete with NO profile attached when the profile was created moments earlier. Verify with: aws ec2 describe-iam-instance-profile-associations --filters Name=instance-id,Values=${instanceId} — and if it is missing, re-associate with: aws ec2 associate-iam-instance-profile --instance-id ${instanceId} --iam-instance-profile ${iamInstanceProfile.arn ? `Arn=${profileRef}` : `Name=${profileRef}`}`);
27437
- } else await this.ensureIamInstanceProfileAssociated(instanceId, iamInstanceProfile.arn, iamInstanceProfile.name, logicalId);
27664
+ if (iamInstanceProfile) {
27665
+ if (process.env["CDKD_NO_WAIT"] === "true") {
27666
+ const profileRef = iamInstanceProfile.arn ?? iamInstanceProfile.name;
27667
+ this.logger.warn(`Skipped the IAM instance profile association check for ${logicalId} (${instanceId}) because --no-wait leaves the instance in 'pending', where AssociateIamInstanceProfile is rejected. RunInstances associates the profile asynchronously and can complete with NO profile attached when the profile was created moments earlier. Verify with: aws ec2 describe-iam-instance-profile-associations --filters Name=instance-id,Values=${instanceId} — and if it is missing, re-associate with: aws ec2 associate-iam-instance-profile --instance-id ${instanceId} --iam-instance-profile ${iamInstanceProfile.arn ? `Arn=${profileRef}` : `Name=${profileRef}`}`);
27668
+ } else await this.ensureIamInstanceProfileAssociated(instanceId, iamInstanceProfile.arn, iamInstanceProfile.name, logicalId);
27669
+ }
27438
27670
  const runningInstance = (await this.ec2Client.send(new DescribeInstancesCommand({ InstanceIds: [instanceId] }))).Reservations?.[0]?.Instances?.[0];
27439
27671
  const attributes = {
27440
27672
  InstanceId: instanceId,
@@ -27644,8 +27876,10 @@ var EC2Provider = class {
27644
27876
  }));
27645
27877
  const newMonitoring = this.coerceBool(properties["Monitoring"]);
27646
27878
  const oldMonitoring = this.coerceBool(previousProperties["Monitoring"]);
27647
- if (newMonitoring !== void 0 && newMonitoring !== oldMonitoring) if (newMonitoring) await this.ec2Client.send(new MonitorInstancesCommand({ InstanceIds: [physicalId] }));
27648
- else await this.ec2Client.send(new UnmonitorInstancesCommand({ InstanceIds: [physicalId] }));
27879
+ if (newMonitoring !== void 0 && newMonitoring !== oldMonitoring) {
27880
+ if (newMonitoring) await this.ec2Client.send(new MonitorInstancesCommand({ InstanceIds: [physicalId] }));
27881
+ else await this.ec2Client.send(new UnmonitorInstancesCommand({ InstanceIds: [physicalId] }));
27882
+ }
27649
27883
  const newMetadata = this.buildMetadataOptions(properties);
27650
27884
  const oldMetadata = this.buildMetadataOptions(previousProperties);
27651
27885
  if (newMetadata !== void 0 && !this.shallowEqual(newMetadata, oldMetadata)) await this.ec2Client.send(new ModifyInstanceMetadataOptionsCommand({
@@ -27691,7 +27925,8 @@ var EC2Provider = class {
27691
27925
  return;
27692
27926
  } catch (error) {
27693
27927
  if (this.isNotFoundError(error)) {
27694
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
27928
+ const clientRegion = await this.ec2Client.config.region();
27929
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
27695
27930
  this.logger.debug(`EC2 Instance ${physicalId} already terminated (not found), treating as success`);
27696
27931
  return;
27697
27932
  }
@@ -27996,7 +28231,8 @@ var EC2Provider = class {
27996
28231
  this.logger.debug(`Successfully deleted NetworkAcl ${logicalId}`);
27997
28232
  } catch (error) {
27998
28233
  if (this.isNotFoundError(error)) {
27999
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
28234
+ const clientRegion = await this.ec2Client.config.region();
28235
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
28000
28236
  this.logger.debug(`NetworkAcl ${physicalId} does not exist, skipping deletion`);
28001
28237
  return;
28002
28238
  }
@@ -28080,7 +28316,8 @@ var EC2Provider = class {
28080
28316
  this.logger.debug(`Successfully deleted NetworkAclEntry ${logicalId}`);
28081
28317
  } catch (error) {
28082
28318
  if (this.isNotFoundError(error)) {
28083
- assertRegionMatch(await this.ec2Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
28319
+ const clientRegion = await this.ec2Client.config.region();
28320
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
28084
28321
  this.logger.debug(`NetworkAclEntry ${physicalId} does not exist, skipping deletion`);
28085
28322
  return;
28086
28323
  }
@@ -28722,8 +28959,10 @@ var EC2Provider = class {
28722
28959
  if (route.DestinationCidrBlock !== void 0) result["DestinationCidrBlock"] = route.DestinationCidrBlock;
28723
28960
  else if (route.DestinationIpv6CidrBlock !== void 0) result["DestinationIpv6CidrBlock"] = route.DestinationIpv6CidrBlock;
28724
28961
  else if (route.DestinationPrefixListId !== void 0) result["DestinationPrefixListId"] = route.DestinationPrefixListId;
28725
- if (route.GatewayId !== void 0) if (route.GatewayId.startsWith("vpce-")) result["VpcEndpointId"] = route.GatewayId;
28726
- else result["GatewayId"] = route.GatewayId;
28962
+ if (route.GatewayId !== void 0) {
28963
+ if (route.GatewayId.startsWith("vpce-")) result["VpcEndpointId"] = route.GatewayId;
28964
+ else result["GatewayId"] = route.GatewayId;
28965
+ }
28727
28966
  if (route.NatGatewayId !== void 0) result["NatGatewayId"] = route.NatGatewayId;
28728
28967
  if (route.InstanceId !== void 0) result["InstanceId"] = route.InstanceId;
28729
28968
  if (route.NetworkInterfaceId !== void 0) result["NetworkInterfaceId"] = route.NetworkInterfaceId;
@@ -29577,7 +29816,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
29577
29816
  this.logger.debug(`Successfully deleted API Gateway Authorizer ${logicalId}`);
29578
29817
  } catch (error) {
29579
29818
  if (error instanceof NotFoundException$1) {
29580
- assertRegionMatch(await this.apiGatewayClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
29819
+ const clientRegion = await this.apiGatewayClient.config.region();
29820
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
29581
29821
  this.logger.debug(`API Gateway Authorizer ${physicalId} does not exist, skipping deletion`);
29582
29822
  return;
29583
29823
  }
@@ -29669,7 +29909,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
29669
29909
  this.logger.debug(`Successfully deleted API Gateway Resource ${logicalId}`);
29670
29910
  } catch (error) {
29671
29911
  if (error instanceof NotFoundException$1) {
29672
- assertRegionMatch(await this.apiGatewayClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
29912
+ const clientRegion = await this.apiGatewayClient.config.region();
29913
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
29673
29914
  this.logger.debug(`API Gateway Resource ${physicalId} does not exist, skipping deletion`);
29674
29915
  return;
29675
29916
  }
@@ -29731,7 +29972,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
29731
29972
  this.logger.debug(`Successfully deleted API Gateway Deployment ${logicalId}`);
29732
29973
  } catch (error) {
29733
29974
  if (error instanceof NotFoundException$1) {
29734
- assertRegionMatch(await this.apiGatewayClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
29975
+ const clientRegion = await this.apiGatewayClient.config.region();
29976
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
29735
29977
  this.logger.debug(`API Gateway Deployment ${physicalId} does not exist, skipping deletion`);
29736
29978
  return;
29737
29979
  }
@@ -29982,7 +30224,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
29982
30224
  this.logger.debug(`Successfully deleted API Gateway Stage ${logicalId}`);
29983
30225
  } catch (error) {
29984
30226
  if (error instanceof NotFoundException$1) {
29985
- assertRegionMatch(await this.apiGatewayClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
30227
+ const clientRegion = await this.apiGatewayClient.config.region();
30228
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
29986
30229
  this.logger.debug(`API Gateway Stage ${physicalId} does not exist, skipping deletion`);
29987
30230
  return;
29988
30231
  }
@@ -30291,7 +30534,8 @@ var ApiGatewayProvider = class ApiGatewayProvider {
30291
30534
  this.logger.debug(`Successfully deleted API Gateway Method ${logicalId}`);
30292
30535
  } catch (error) {
30293
30536
  if (error instanceof NotFoundException$1) {
30294
- assertRegionMatch(await this.apiGatewayClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
30537
+ const clientRegion = await this.apiGatewayClient.config.region();
30538
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
30295
30539
  this.logger.debug(`API Gateway Method ${physicalId} does not exist, skipping deletion`);
30296
30540
  return;
30297
30541
  }
@@ -30990,11 +31234,13 @@ var ApiGatewayV2Provider = class {
30990
31234
  const apiId = response.ApiId;
30991
31235
  const apiEndpoint = response.ApiEndpoint;
30992
31236
  this.logger.debug(`Successfully created API Gateway V2 Api ${logicalId}: ${apiId}`);
31237
+ const executeApiArn = await this.buildExecuteApiArn(apiId);
30993
31238
  return {
30994
31239
  physicalId: apiId,
30995
31240
  attributes: {
30996
31241
  ApiId: apiId,
30997
- ApiEndpoint: apiEndpoint
31242
+ ApiEndpoint: apiEndpoint,
31243
+ ...executeApiArn !== void 0 ? { ExecuteApiArn: executeApiArn } : {}
30998
31244
  }
30999
31245
  };
31000
31246
  } catch (error) {
@@ -31009,7 +31255,8 @@ var ApiGatewayV2Provider = class {
31009
31255
  this.logger.debug(`Successfully deleted API Gateway V2 Api ${logicalId}`);
31010
31256
  } catch (error) {
31011
31257
  if (error instanceof NotFoundException$3) {
31012
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
31258
+ const clientRegion = await this.getClient().config.region();
31259
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
31013
31260
  this.logger.debug(`API Gateway V2 Api ${physicalId} does not exist, skipping deletion`);
31014
31261
  return;
31015
31262
  }
@@ -31017,6 +31264,52 @@ var ApiGatewayV2Provider = class {
31017
31264
  throw new ProvisioningError(`Failed to delete API Gateway V2 Api ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
31018
31265
  }
31019
31266
  }
31267
+ /**
31268
+ * `arn:{partition}:execute-api:{region}:{account}:{apiId}` — the ARN AWS
31269
+ * documents for an API Gateway v2 API, which no API call returns.
31270
+ *
31271
+ * Refuses to record a FABRICATED account, exactly as
31272
+ * `SSMParameterProvider.buildParameterArn` does: an ARN naming a placeholder
31273
+ * account is worse than an absent one, because it is persisted into
31274
+ * `state.json` and outlives the deploy that produced it. The partition is
31275
+ * DERIVED through the same closed region mapping `${AWS::Partition}` uses,
31276
+ * and the region segment is canonicalized — defence in depth rather than the
31277
+ * only fold, since `getAccountInfo` already canonicalizes what it returns
31278
+ * (the issue #1795 / #1814 class: an unfolded `US-EAST-1` matches no IAM
31279
+ * policy and is persisted).
31280
+ *
31281
+ * **NEVER THROWS, and callers depend on it.** Every call site is INSIDE the
31282
+ * `try` of an operation that has already created or found the API, so an
31283
+ * escape here would report a failed create over a resource that exists —
31284
+ * an orphan. A failure means the attribute is absent, which is the state
31285
+ * every deploy before this change was in.
31286
+ *
31287
+ * Takes NO `SecretMaskingContext`, where its SSM twin takes one, and the
31288
+ * difference is not an oversight: every interpolated value here is
31289
+ * non-secret BY CONSTRUCTION — an AWS-minted api id (or, on import, the
31290
+ * CLI's own `--resource` argument), a derived partition, a region, an
31291
+ * account id — and the catch names the error CLASS rather than its message.
31292
+ * `SSMParameterProvider.buildParameterArn` needs a masker because it
31293
+ * interpolates `error.message`, which quotes a resolved parameter value
31294
+ * back. **Widening this catch to `error.message` therefore requires
31295
+ * threading the masker in the same change**; the invariant is what makes
31296
+ * the omission safe, not the current absence of a caller.
31297
+ */
31298
+ async buildExecuteApiArn(apiId) {
31299
+ try {
31300
+ const region = await this.getClient().config.region();
31301
+ const accountInfo = await getAccountInfo(region);
31302
+ if (accountInfo.fabricated) {
31303
+ this.logger.warn(`Cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so the ExecuteApiArn attribute for API ${apiId} would be fabricated and is NOT recorded. An Fn::GetAtt on it will fail until a later deploy resolves the account.`);
31304
+ return;
31305
+ }
31306
+ const { partition } = derivePartitionAndUrlSuffix(accountInfo.region);
31307
+ return `arn:${partition}:execute-api:${canonicalizeRegion(accountInfo.region)}:${accountInfo.accountId}:${apiId}`;
31308
+ } catch (error) {
31309
+ this.logger.warn(`Could not build the ExecuteApiArn attribute for API ${apiId} (${error instanceof Error ? error.name : typeof error}), so it is NOT recorded. An Fn::GetAtt on it will fail until a later deploy records it.`);
31310
+ return;
31311
+ }
31312
+ }
31020
31313
  getApiAttribute(physicalId, attributeName) {
31021
31314
  if (attributeName === "ApiId") return physicalId;
31022
31315
  }
@@ -31061,7 +31354,8 @@ var ApiGatewayV2Provider = class {
31061
31354
  this.logger.debug(`Successfully deleted API Gateway V2 Stage ${logicalId}`);
31062
31355
  } catch (error) {
31063
31356
  if (error instanceof NotFoundException$3) {
31064
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
31357
+ const clientRegion = await this.getClient().config.region();
31358
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
31065
31359
  this.logger.debug(`API Gateway V2 Stage ${physicalId} does not exist, skipping deletion`);
31066
31360
  return;
31067
31361
  }
@@ -31121,7 +31415,8 @@ var ApiGatewayV2Provider = class {
31121
31415
  this.logger.debug(`Successfully deleted API Gateway V2 Integration ${logicalId}`);
31122
31416
  } catch (error) {
31123
31417
  if (error instanceof NotFoundException$3) {
31124
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
31418
+ const clientRegion = await this.getClient().config.region();
31419
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
31125
31420
  this.logger.debug(`API Gateway V2 Integration ${physicalId} does not exist, skipping deletion`);
31126
31421
  return;
31127
31422
  }
@@ -31174,7 +31469,8 @@ var ApiGatewayV2Provider = class {
31174
31469
  this.logger.debug(`Successfully deleted API Gateway V2 Route ${logicalId}`);
31175
31470
  } catch (error) {
31176
31471
  if (error instanceof NotFoundException$3) {
31177
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
31472
+ const clientRegion = await this.getClient().config.region();
31473
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
31178
31474
  this.logger.debug(`API Gateway V2 Route ${physicalId} does not exist, skipping deletion`);
31179
31475
  return;
31180
31476
  }
@@ -31227,7 +31523,8 @@ var ApiGatewayV2Provider = class {
31227
31523
  this.logger.debug(`Successfully deleted API Gateway V2 Authorizer ${logicalId}`);
31228
31524
  } catch (error) {
31229
31525
  if (error instanceof NotFoundException$3) {
31230
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
31526
+ const clientRegion = await this.getClient().config.region();
31527
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
31231
31528
  this.logger.debug(`API Gateway V2 Authorizer ${physicalId} does not exist, skipping deletion`);
31232
31529
  return;
31233
31530
  }
@@ -31437,10 +31734,15 @@ var ApiGatewayV2Provider = class {
31437
31734
  }
31438
31735
  const explicit = resolveExplicitPhysicalId(input, null);
31439
31736
  if (explicit) try {
31440
- await this.getClient().send(new GetApiCommand({ ApiId: explicit }));
31737
+ const got = await this.getClient().send(new GetApiCommand({ ApiId: explicit }));
31738
+ const executeApiArn = await this.buildExecuteApiArn(explicit);
31441
31739
  return {
31442
31740
  physicalId: explicit,
31443
- attributes: {}
31741
+ attributes: {
31742
+ ApiId: explicit,
31743
+ ...got.ApiEndpoint !== void 0 ? { ApiEndpoint: got.ApiEndpoint } : {},
31744
+ ...executeApiArn !== void 0 ? { ExecuteApiArn: executeApiArn } : {}
31745
+ }
31444
31746
  };
31445
31747
  } catch (err) {
31446
31748
  if (err instanceof NotFoundException$3) return null;
@@ -31519,11 +31821,25 @@ var ApiGatewayV2Provider = class {
31519
31821
  }
31520
31822
  this.logger.debug(`Updating API Gateway V2 Api ${logicalId}: ${physicalId}`);
31521
31823
  try {
31522
- if (changed) await this.getClient().send(new UpdateApiCommand(input));
31824
+ let apiEndpoint;
31825
+ if (changed) apiEndpoint = (await this.getClient().send(new UpdateApiCommand(input))).ApiEndpoint;
31523
31826
  if (corsRemoved) {
31524
31827
  this.logger.debug(`Clearing CORS configuration for Api ${logicalId}: ${physicalId}`);
31525
31828
  await this.getClient().send(new DeleteCorsConfigurationCommand({ ApiId: physicalId }));
31526
31829
  }
31830
+ if (apiEndpoint !== void 0) {
31831
+ const executeApiArn = await this.buildExecuteApiArn(physicalId);
31832
+ if (executeApiArn !== void 0) return {
31833
+ physicalId,
31834
+ wasReplaced: false,
31835
+ attributes: {
31836
+ ApiId: physicalId,
31837
+ ApiEndpoint: apiEndpoint,
31838
+ ExecuteApiArn: executeApiArn
31839
+ }
31840
+ };
31841
+ this.logger.debug(`Skipping the ExecuteApiArn heal for API ${physicalId}: the ARN could not be built. The previously recorded attributes are kept.`);
31842
+ }
31527
31843
  return {
31528
31844
  physicalId,
31529
31845
  wasReplaced: false
@@ -32368,7 +32684,8 @@ var CloudFrontOAIProvider = class {
32368
32684
  etag = (await this.cloudFrontClient.send(new GetCloudFrontOriginAccessIdentityCommand({ Id: physicalId }))).ETag;
32369
32685
  } catch (error) {
32370
32686
  if (error instanceof NoSuchCloudFrontOriginAccessIdentity) {
32371
- assertRegionMatch(await this.cloudFrontClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
32687
+ const clientRegion = await this.cloudFrontClient.config.region();
32688
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
32372
32689
  this.logger.debug(`OAI ${physicalId} does not exist, skipping deletion`);
32373
32690
  return;
32374
32691
  }
@@ -32381,7 +32698,8 @@ var CloudFrontOAIProvider = class {
32381
32698
  this.logger.debug(`Successfully deleted CloudFront OAI ${logicalId}`);
32382
32699
  } catch (error) {
32383
32700
  if (error instanceof NoSuchCloudFrontOriginAccessIdentity) {
32384
- assertRegionMatch(await this.cloudFrontClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
32701
+ const clientRegion = await this.cloudFrontClient.config.region();
32702
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
32385
32703
  this.logger.debug(`OAI ${physicalId} does not exist, skipping deletion`);
32386
32704
  return;
32387
32705
  }
@@ -32705,7 +33023,8 @@ var CloudFrontOACProvider = class {
32705
33023
  * region matches the region the resource was recorded in.
32706
33024
  */
32707
33025
  async assertDeleteRegion(resourceType, logicalId, physicalId, context) {
32708
- assertRegionMatch(await this.cloudFrontClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
33026
+ const clientRegion = await this.cloudFrontClient.config.region();
33027
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
32709
33028
  }
32710
33029
  };
32711
33030
 
@@ -33012,7 +33331,8 @@ var CloudFrontDistributionProvider = class {
33012
33331
  config = getConfigResponse.DistributionConfig;
33013
33332
  } catch (error) {
33014
33333
  if (error instanceof NoSuchDistribution) {
33015
- assertRegionMatch(await this.cloudFrontClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
33334
+ const clientRegion = await this.cloudFrontClient.config.region();
33335
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
33016
33336
  this.logger.debug(`Distribution ${physicalId} does not exist, skipping deletion`);
33017
33337
  return;
33018
33338
  }
@@ -33039,7 +33359,8 @@ var CloudFrontDistributionProvider = class {
33039
33359
  this.logger.debug(`Successfully deleted CloudFront Distribution ${logicalId}`);
33040
33360
  } catch (error) {
33041
33361
  if (error instanceof NoSuchDistribution) {
33042
- assertRegionMatch(await this.cloudFrontClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
33362
+ const clientRegion = await this.cloudFrontClient.config.region();
33363
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
33043
33364
  this.logger.debug(`Distribution ${physicalId} does not exist, skipping deletion`);
33044
33365
  return;
33045
33366
  }
@@ -33178,7 +33499,7 @@ var CloudFrontDistributionProvider = class {
33178
33499
  * warning applies), `false` on budget exhaustion.
33179
33500
  */
33180
33501
  async waitForDistributionStable(distributionId, expectedEnabled) {
33181
- const budgetMs = Math.max(1200 * 1e3, resolvedResourceTimeoutMs("AWS::CloudFront::Distribution") ?? 0);
33502
+ const budgetMs = Math.max(12e5, resolvedResourceTimeoutMs("AWS::CloudFront::Distribution") ?? 0);
33182
33503
  const deadline = Date.now() + budgetMs;
33183
33504
  let delay = 5e3;
33184
33505
  const maxDelay = 1e4;
@@ -33943,7 +34264,8 @@ var AgentCoreRuntimeProvider = class {
33943
34264
  this.logger.debug(`Successfully deleted BedrockAgentCore Runtime ${logicalId}`);
33944
34265
  } catch (error) {
33945
34266
  if (error instanceof ResourceNotFoundException$6) {
33946
- assertRegionMatch(await this.client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
34267
+ const clientRegion = await this.client.config.region();
34268
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
33947
34269
  this.logger.debug(`Runtime ${physicalId} does not exist, skipping deletion`);
33948
34270
  return;
33949
34271
  }
@@ -34513,7 +34835,8 @@ var AgentCoreEvaluatorProvider = class {
34513
34835
  this.logger.debug(`Successfully deleted BedrockAgentCore Evaluator ${logicalId}`);
34514
34836
  } catch (error) {
34515
34837
  if (error instanceof ResourceNotFoundException$6) {
34516
- assertRegionMatch(await this.client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
34838
+ const clientRegion = await this.client.config.region();
34839
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
34517
34840
  this.logger.debug(`Evaluator ${physicalId} does not exist, skipping deletion`);
34518
34841
  return;
34519
34842
  }
@@ -34728,7 +35051,8 @@ var StepFunctionsProvider = class {
34728
35051
  this.logger.debug(`Successfully deleted Step Functions state machine ${logicalId}`);
34729
35052
  } catch (error) {
34730
35053
  if (error instanceof StateMachineDoesNotExist) {
34731
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
35054
+ const clientRegion = await this.getClient().config.region();
35055
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
34732
35056
  this.logger.debug(`Step Functions state machine ${physicalId} does not exist, skipping deletion`);
34733
35057
  return;
34734
35058
  }
@@ -34797,7 +35121,8 @@ var StepFunctionsProvider = class {
34797
35121
  result["EncryptionConfiguration"] = ec;
34798
35122
  }
34799
35123
  try {
34800
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$11({ resourceArn: physicalId }))).tags);
35124
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$11({ resourceArn: physicalId }));
35125
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.tags);
34801
35126
  } catch (err) {
34802
35127
  if (!(err instanceof StateMachineDoesNotExist)) throw err;
34803
35128
  }
@@ -35376,7 +35701,8 @@ var ECSProvider = class {
35376
35701
  this.logger.debug(`Successfully deleted ECS cluster ${logicalId}`);
35377
35702
  } catch (error) {
35378
35703
  if (this.isClusterNotFoundException(error)) {
35379
- assertRegionMatch(await client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
35704
+ const clientRegion = await client.config.region();
35705
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
35380
35706
  this.logger.debug(`ECS cluster ${physicalId} not found, skipping deletion`);
35381
35707
  return;
35382
35708
  }
@@ -35437,7 +35763,8 @@ var ECSProvider = class {
35437
35763
  this.logger.debug(`Successfully deregistered ECS task definition ${logicalId}`);
35438
35764
  } catch (error) {
35439
35765
  if (this.isNotFoundException(error)) {
35440
- assertRegionMatch(await client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
35766
+ const clientRegion = await client.config.region();
35767
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
35441
35768
  this.logger.debug(`ECS task definition ${physicalId} not found, skipping deletion`);
35442
35769
  return;
35443
35770
  }
@@ -35659,7 +35986,8 @@ var ECSProvider = class {
35659
35986
  this.logger.debug(`Scaled down ECS service ${physicalId} to 0`);
35660
35987
  } catch (error) {
35661
35988
  if (this.isServiceNotFoundException(error)) {
35662
- assertRegionMatch(await client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
35989
+ const clientRegion = await client.config.region();
35990
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
35663
35991
  this.logger.debug(`ECS service ${physicalId} not found during scale down, skipping deletion`);
35664
35992
  return;
35665
35993
  }
@@ -35673,7 +36001,8 @@ var ECSProvider = class {
35673
36001
  this.logger.debug(`Successfully deleted ECS service ${logicalId}`);
35674
36002
  } catch (error) {
35675
36003
  if (this.isServiceNotFoundException(error)) {
35676
- assertRegionMatch(await client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
36004
+ const clientRegion = await client.config.region();
36005
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
35677
36006
  this.logger.debug(`ECS service ${physicalId} not found, skipping deletion`);
35678
36007
  return;
35679
36008
  }
@@ -37267,15 +37596,18 @@ var ELBv2Provider = class {
37267
37596
  for (const [k, v] of Object.entries(p)) if (!handledKeys.has(k)) out[k] = v;
37268
37597
  return out;
37269
37598
  };
37270
- if (JSON.stringify(stripHandled(properties)) !== JSON.stringify(stripHandled(previousProperties))) throw new ResourceUpdateNotSupportedError("AWS::ElasticLoadBalancingV2::LoadBalancer", logicalId, "ELBv2 LoadBalancer Name / Type / Scheme are immutable on AWS — none of the ELBv2 Modify* / Set* APIs accept these fields; they are fixed at creation. cdkd handles LoadBalancerAttributes / Subnets / SubnetMappings / SecurityGroups / IpAddressType / Tags in-place. " + (this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]).some((a) => a.Key === "deletion_protection.enabled" && isTruthyCfnBoolean(a.Value)) ? protectedReplacementAdvice({
37271
- evidence: "cdkd's recorded properties for this load balancer carry LoadBalancerAttributes deletion_protection.enabled=true",
37272
- replaceFlags: "cdkd deploy --replace",
37273
- disable: {
37274
- before: "aws elbv2 modify-load-balancer-attributes --load-balancer-arn",
37275
- identifier: physicalId,
37276
- after: "--attributes Key=deletion_protection.enabled,Value=false"
37277
- }
37278
- }) : "For Name / Type / Scheme re-deploy with cdkd deploy --replace, or destroy + redeploy the stack."));
37599
+ if (JSON.stringify(stripHandled(properties)) !== JSON.stringify(stripHandled(previousProperties))) {
37600
+ const remedy = this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]).some((a) => a.Key === "deletion_protection.enabled" && isTruthyCfnBoolean(a.Value)) ? protectedReplacementAdvice({
37601
+ evidence: "cdkd's recorded properties for this load balancer carry LoadBalancerAttributes deletion_protection.enabled=true",
37602
+ replaceFlags: "cdkd deploy --replace",
37603
+ disable: {
37604
+ before: "aws elbv2 modify-load-balancer-attributes --load-balancer-arn",
37605
+ identifier: physicalId,
37606
+ after: "--attributes Key=deletion_protection.enabled,Value=false"
37607
+ }
37608
+ }) : "For Name / Type / Scheme re-deploy with cdkd deploy --replace, or destroy + redeploy the stack.";
37609
+ throw new ResourceUpdateNotSupportedError("AWS::ElasticLoadBalancingV2::LoadBalancer", logicalId, "ELBv2 LoadBalancer Name / Type / Scheme are immutable on AWS — none of the ELBv2 Modify* / Set* APIs accept these fields; they are fixed at creation. cdkd handles LoadBalancerAttributes / Subnets / SubnetMappings / SecurityGroups / IpAddressType / Tags in-place. " + remedy);
37610
+ }
37279
37611
  const submittedAttrs = this.diffAttributes(this.normalizeAttributes(properties["LoadBalancerAttributes"]), this.normalizeAttributes(previousProperties["LoadBalancerAttributes"]), this.attributeRemovalResolver(LOAD_BALANCER_ATTRIBUTE_DEFAULTS));
37280
37612
  if (submittedAttrs.length > 0) {
37281
37613
  await this.getClient().send(new ModifyLoadBalancerAttributesCommand({
@@ -37409,7 +37741,8 @@ var ELBv2Provider = class {
37409
37741
  this.logger.debug(`Successfully deleted LoadBalancer ${logicalId}`);
37410
37742
  } catch (error) {
37411
37743
  if (this.isNotFoundError(error)) {
37412
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
37744
+ const clientRegion = await this.getClient().config.region();
37745
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
37413
37746
  this.logger.debug(`LoadBalancer ${physicalId} does not exist, skipping deletion`);
37414
37747
  return;
37415
37748
  }
@@ -37574,7 +37907,8 @@ var ELBv2Provider = class {
37574
37907
  this.logger.debug(`Successfully deleted TargetGroup ${logicalId}`);
37575
37908
  } catch (error) {
37576
37909
  if (this.isNotFoundError(error)) {
37577
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
37910
+ const clientRegion = await this.getClient().config.region();
37911
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
37578
37912
  this.logger.debug(`TargetGroup ${physicalId} does not exist, skipping deletion`);
37579
37913
  return;
37580
37914
  }
@@ -37694,7 +38028,8 @@ var ELBv2Provider = class {
37694
38028
  this.logger.debug(`Successfully deleted Listener ${logicalId}`);
37695
38029
  } catch (error) {
37696
38030
  if (this.isNotFoundError(error)) {
37697
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
38031
+ const clientRegion = await this.getClient().config.region();
38032
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
37698
38033
  this.logger.debug(`Listener ${physicalId} does not exist, skipping deletion`);
37699
38034
  return;
37700
38035
  }
@@ -38424,7 +38759,8 @@ var RDSProvider = class {
38424
38759
  this.logger.debug(`Successfully deleted DBSubnetGroup ${logicalId}`);
38425
38760
  } catch (error) {
38426
38761
  if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
38427
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
38762
+ const clientRegion = await this.getClient().config.region();
38763
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
38428
38764
  this.logger.debug(`DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
38429
38765
  return;
38430
38766
  }
@@ -38582,7 +38918,8 @@ var RDSProvider = class {
38582
38918
  await this.waitForClusterDeleted(physicalId);
38583
38919
  } catch (error) {
38584
38920
  if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
38585
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
38921
+ const clientRegion = await this.getClient().config.region();
38922
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
38586
38923
  this.logger.debug(`DBCluster ${physicalId} does not exist, skipping deletion`);
38587
38924
  return;
38588
38925
  }
@@ -38709,7 +39046,8 @@ var RDSProvider = class {
38709
39046
  await this.waitForInstanceDeleted(physicalId);
38710
39047
  } catch (error) {
38711
39048
  if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
38712
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
39049
+ const clientRegion = await this.getClient().config.region();
39050
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
38713
39051
  this.logger.debug(`DBInstance ${physicalId} does not exist, skipping deletion`);
38714
39052
  return;
38715
39053
  }
@@ -38980,7 +39318,8 @@ var RDSProvider = class {
38980
39318
  */
38981
39319
  async attachTags(result, arn) {
38982
39320
  try {
38983
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$10({ ResourceName: arn }))).TagList);
39321
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$10({ ResourceName: arn }));
39322
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
38984
39323
  } catch (err) {
38985
39324
  this.logger.debug(`RDS ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
38986
39325
  }
@@ -39032,7 +39371,7 @@ var RDSProvider = class {
39032
39371
  //#endregion
39033
39372
  //#region src/provisioning/providers/rds-dbproxy-provider.ts
39034
39373
  const POLL_INTERVAL_MS$1 = 5e3;
39035
- const POLL_TIMEOUT_MS$1 = 1800 * 1e3;
39374
+ const POLL_TIMEOUT_MS$1 = 18e5;
39036
39375
  /**
39037
39376
  * AWS RDS DBProxy Provider
39038
39377
  *
@@ -39195,7 +39534,8 @@ var RDSDBProxyProvider = class {
39195
39534
  await client.send(new DeleteDBProxyCommand({ DBProxyName: physicalId }));
39196
39535
  } catch (error) {
39197
39536
  if (error instanceof DBProxyNotFoundFault) {
39198
- assertRegionMatch(await client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
39537
+ const clientRegion = await client.config.region();
39538
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
39199
39539
  this.logger.debug(`DBProxy ${physicalId} already gone, treating as success`);
39200
39540
  return;
39201
39541
  }
@@ -39278,7 +39618,8 @@ var RDSDBProxyProvider = class {
39278
39618
  }))
39279
39619
  };
39280
39620
  if (p.DBProxyArn) try {
39281
- result["Tags"] = normalizeAwsTagsToCfn((await client.send(new ListTagsForResourceCommand$10({ ResourceName: p.DBProxyArn }))).TagList ?? []);
39621
+ const tagResp = await client.send(new ListTagsForResourceCommand$10({ ResourceName: p.DBProxyArn }));
39622
+ result["Tags"] = normalizeAwsTagsToCfn(tagResp.TagList ?? []);
39282
39623
  } catch (error) {
39283
39624
  this.logger.debug(`ListTagsForResource failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
39284
39625
  result["Tags"] = [];
@@ -39370,7 +39711,7 @@ var RDSDBProxyProvider = class {
39370
39711
  //#endregion
39371
39712
  //#region src/provisioning/providers/rds-dbproxy-endpoint-provider.ts
39372
39713
  const POLL_INTERVAL_MS = 5e3;
39373
- const POLL_TIMEOUT_MS = 1800 * 1e3;
39714
+ const POLL_TIMEOUT_MS = 18e5;
39374
39715
  /**
39375
39716
  * AWS RDS DBProxyEndpoint Provider
39376
39717
  *
@@ -39518,7 +39859,8 @@ var RDSDBProxyEndpointProvider = class {
39518
39859
  await client.send(new DeleteDBProxyEndpointCommand({ DBProxyEndpointName: physicalId }));
39519
39860
  } catch (error) {
39520
39861
  if (error instanceof DBProxyEndpointNotFoundFault || error instanceof DBProxyNotFoundFault) {
39521
- assertRegionMatch(await client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
39862
+ const clientRegion = await client.config.region();
39863
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
39522
39864
  this.logger.debug(`DBProxyEndpoint ${physicalId} or parent already gone, treating as success`);
39523
39865
  return;
39524
39866
  }
@@ -39587,7 +39929,8 @@ var RDSDBProxyEndpointProvider = class {
39587
39929
  TargetRole: e.TargetRole ?? "READ_WRITE"
39588
39930
  };
39589
39931
  if (e.DBProxyEndpointArn) try {
39590
- result["Tags"] = normalizeAwsTagsToCfn((await client.send(new ListTagsForResourceCommand$10({ ResourceName: e.DBProxyEndpointArn }))).TagList ?? []);
39932
+ const tagResp = await client.send(new ListTagsForResourceCommand$10({ ResourceName: e.DBProxyEndpointArn }));
39933
+ result["Tags"] = normalizeAwsTagsToCfn(tagResp.TagList ?? []);
39591
39934
  } catch (error) {
39592
39935
  this.logger.debug(`ListTagsForResource failed for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
39593
39936
  result["Tags"] = [];
@@ -39880,7 +40223,8 @@ var RDSDBProxyTargetGroupProvider = class {
39880
40223
  }));
39881
40224
  } catch (error) {
39882
40225
  if (error instanceof DBProxyNotFoundFault || error instanceof DBProxyTargetGroupNotFoundFault || error instanceof DBProxyTargetNotFoundFault) {
39883
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
40226
+ const clientRegion = await this.getClient().config.region();
40227
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
39884
40228
  this.logger.debug(`${dbProxyName}/${targetGroupName} or its target is already gone, treating as success`);
39885
40229
  return;
39886
40230
  }
@@ -40129,7 +40473,8 @@ var DocDBProvider = class {
40129
40473
  this.logger.debug(`Successfully deleted DocDB DBSubnetGroup ${logicalId}`);
40130
40474
  } catch (error) {
40131
40475
  if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
40132
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
40476
+ const clientRegion = await this.getClient().config.region();
40477
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
40133
40478
  this.logger.debug(`DocDB DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
40134
40479
  return;
40135
40480
  }
@@ -40245,7 +40590,8 @@ var DocDBProvider = class {
40245
40590
  await this.waitForClusterDeleted(physicalId);
40246
40591
  } catch (error) {
40247
40592
  if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
40248
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
40593
+ const clientRegion = await this.getClient().config.region();
40594
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
40249
40595
  this.logger.debug(`DocDB DBCluster ${physicalId} does not exist, skipping deletion`);
40250
40596
  return;
40251
40597
  }
@@ -40323,7 +40669,8 @@ var DocDBProvider = class {
40323
40669
  await this.waitForInstanceDeleted(physicalId);
40324
40670
  } catch (error) {
40325
40671
  if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
40326
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
40672
+ const clientRegion = await this.getClient().config.region();
40673
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
40327
40674
  this.logger.debug(`DocDB DBInstance ${physicalId} does not exist, skipping deletion`);
40328
40675
  return;
40329
40676
  }
@@ -40564,7 +40911,8 @@ var DocDBProvider = class {
40564
40911
  */
40565
40912
  async attachTags(result, arn) {
40566
40913
  try {
40567
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$12({ ResourceName: arn }))).TagList);
40914
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$12({ ResourceName: arn }));
40915
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
40568
40916
  } catch (err) {
40569
40917
  this.logger.debug(`DocDB ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
40570
40918
  }
@@ -40765,7 +41113,8 @@ var NeptuneProvider = class {
40765
41113
  this.logger.debug(`Successfully deleted Neptune DBSubnetGroup ${logicalId}`);
40766
41114
  } catch (error) {
40767
41115
  if (this.isNotFoundError(error, "DBSubnetGroupNotFoundFault")) {
40768
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
41116
+ const clientRegion = await this.getClient().config.region();
41117
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
40769
41118
  this.logger.debug(`Neptune DBSubnetGroup ${physicalId} does not exist, skipping deletion`);
40770
41119
  return;
40771
41120
  }
@@ -40878,7 +41227,8 @@ var NeptuneProvider = class {
40878
41227
  await this.waitForClusterDeleted(physicalId);
40879
41228
  } catch (error) {
40880
41229
  if (this.isNotFoundError(error, "DBClusterNotFoundFault")) {
40881
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
41230
+ const clientRegion = await this.getClient().config.region();
41231
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
40882
41232
  this.logger.debug(`Neptune DBCluster ${physicalId} does not exist, skipping deletion`);
40883
41233
  return;
40884
41234
  }
@@ -40972,7 +41322,8 @@ var NeptuneProvider = class {
40972
41322
  await this.waitForInstanceDeleted(physicalId);
40973
41323
  } catch (error) {
40974
41324
  if (this.isNotFoundError(error, "DBInstanceNotFoundFault")) {
40975
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
41325
+ const clientRegion = await this.getClient().config.region();
41326
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
40976
41327
  this.logger.debug(`Neptune DBInstance ${physicalId} does not exist, skipping deletion`);
40977
41328
  return;
40978
41329
  }
@@ -41203,7 +41554,8 @@ var NeptuneProvider = class {
41203
41554
  */
41204
41555
  async attachTags(result, arn) {
41205
41556
  try {
41206
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$13({ ResourceName: arn }))).TagList);
41557
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$13({ ResourceName: arn }));
41558
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
41207
41559
  } catch (err) {
41208
41560
  this.logger.debug(`Neptune ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
41209
41561
  }
@@ -41666,7 +42018,8 @@ var Route53Provider = class {
41666
42018
  this.logger.debug(`Successfully deleted hosted zone ${logicalId}`);
41667
42019
  } catch (error) {
41668
42020
  if (error instanceof Error && error.name === "NoSuchHostedZone") {
41669
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
42021
+ const clientRegion = await this.getClient().config.region();
42022
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
41670
42023
  this.logger.debug(`Hosted zone ${physicalId} does not exist, skipping deletion`);
41671
42024
  return;
41672
42025
  }
@@ -41863,7 +42216,8 @@ var Route53Provider = class {
41863
42216
  hostedZoneId = await this.resolveHostedZoneId(properties, logicalId, resourceType, physicalId, { requireUnambiguousZoneName: true });
41864
42217
  } catch (error) {
41865
42218
  if (error instanceof HostedZoneNameNotFoundError) {
41866
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
42219
+ const clientRegion = await this.getClient().config.region();
42220
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
41867
42221
  this.logger.warn(`Hosted zone "${String(properties?.["HostedZoneName"])}" for record set ${logicalId} (${physicalId}) no longer exists; treating the record as already deleted and dropping it from state.`);
41868
42222
  return;
41869
42223
  }
@@ -41889,7 +42243,8 @@ var Route53Provider = class {
41889
42243
  this.logger.debug(`Successfully deleted record set ${logicalId}`);
41890
42244
  } catch (error) {
41891
42245
  if (error instanceof Error && (error.name === "InvalidChangeBatch" || error.message.includes("it was not found"))) {
41892
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
42246
+ const clientRegion = await this.getClient().config.region();
42247
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
41893
42248
  this.logger.debug(`Record set ${physicalId} does not exist, skipping deletion`);
41894
42249
  return;
41895
42250
  }
@@ -42362,10 +42717,11 @@ var Route53Provider = class {
42362
42717
  });
42363
42718
  const idTail = physicalId.replace(/^\/hostedzone\//, "");
42364
42719
  try {
42365
- result["HostedZoneTags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$8({
42720
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$8({
42366
42721
  ResourceType: "hostedzone",
42367
42722
  ResourceId: idTail
42368
- }))).ResourceTagSet?.Tags);
42723
+ }));
42724
+ result["HostedZoneTags"] = normalizeAwsTagsToCfn(tagsResp.ResourceTagSet?.Tags);
42369
42725
  } catch (err) {
42370
42726
  this.logger.debug(`Route53 ListTagsForResource(${idTail}) failed: ${err instanceof Error ? err.message : String(err)}`);
42371
42727
  }
@@ -44028,7 +44384,8 @@ var CognitoUserPoolProvider = class {
44028
44384
  needsFlip = (await this.getClient().send(new DescribeUserPoolCommand({ UserPoolId: physicalId }))).UserPool?.DeletionProtection === "ACTIVE";
44029
44385
  } catch (descError) {
44030
44386
  if (descError instanceof ResourceNotFoundException$9) {
44031
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
44387
+ const clientRegion = await this.getClient().config.region();
44388
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
44032
44389
  this.logger.debug(`Cognito User Pool ${physicalId} does not exist, skipping deletion`);
44033
44390
  return;
44034
44391
  }
@@ -44051,7 +44408,8 @@ var CognitoUserPoolProvider = class {
44051
44408
  this.logger.debug(`Successfully deleted Cognito User Pool ${logicalId}`);
44052
44409
  } catch (error) {
44053
44410
  if (error instanceof ResourceNotFoundException$9) {
44054
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
44411
+ const clientRegion = await this.getClient().config.region();
44412
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
44055
44413
  this.logger.debug(`Cognito User Pool ${physicalId} does not exist, skipping deletion`);
44056
44414
  return;
44057
44415
  }
@@ -44302,7 +44660,8 @@ var ElastiCacheProvider = class {
44302
44660
  this.logger.debug(`Successfully deleted CacheSubnetGroup ${logicalId}`);
44303
44661
  } catch (error) {
44304
44662
  if (this.isNotFoundError(error, "CacheSubnetGroupNotFoundFault")) {
44305
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
44663
+ const clientRegion = await this.getClient().config.region();
44664
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
44306
44665
  this.logger.debug(`CacheSubnetGroup ${physicalId} does not exist, skipping deletion`);
44307
44666
  return;
44308
44667
  }
@@ -44428,7 +44787,8 @@ var ElastiCacheProvider = class {
44428
44787
  await this.waitForClusterDeleted(physicalId);
44429
44788
  } catch (error) {
44430
44789
  if (this.isNotFoundError(error, "CacheClusterNotFoundFault")) {
44431
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
44790
+ const clientRegion = await this.getClient().config.region();
44791
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
44432
44792
  this.logger.debug(`CacheCluster ${physicalId} does not exist, skipping deletion`);
44433
44793
  return;
44434
44794
  }
@@ -44635,7 +44995,8 @@ var ElastiCacheProvider = class {
44635
44995
  /** Best-effort tag fetch — failures omit the key without breaking the read. */
44636
44996
  async attachTags(result, arn) {
44637
44997
  try {
44638
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$7({ ResourceName: arn }))).TagList);
44998
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$7({ ResourceName: arn }));
44999
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.TagList);
44639
45000
  } catch (err) {
44640
45001
  this.logger.debug(`ElastiCache ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
44641
45002
  }
@@ -44969,7 +45330,8 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
44969
45330
  this.logger.debug(`Successfully deleted Cloud Map namespace ${logicalId}`);
44970
45331
  } catch (error) {
44971
45332
  if (error instanceof NamespaceNotFound) {
44972
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
45333
+ const clientRegion = await this.getClient().config.region();
45334
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
44973
45335
  this.logger.debug(`Namespace ${physicalId} does not exist, skipping deletion`);
44974
45336
  return;
44975
45337
  }
@@ -45299,7 +45661,8 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
45299
45661
  this.logger.debug(`Successfully deleted service discovery service ${logicalId}`);
45300
45662
  } catch (error) {
45301
45663
  if (error instanceof ServiceNotFound) {
45302
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
45664
+ const clientRegion = await this.getClient().config.region();
45665
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
45303
45666
  this.logger.debug(`Service ${physicalId} does not exist, skipping deletion`);
45304
45667
  return;
45305
45668
  }
@@ -45430,7 +45793,10 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
45430
45793
  if (targets) return targets["NAMESPACE"] || targets["SERVICE"] || operationId;
45431
45794
  return operationId;
45432
45795
  }
45433
- if (status === "FAIL") throw new ProvisioningError(`Operation failed for ${logicalId}: ${maskerOrIdentity(maskSecrets)(result.Operation?.ErrorMessage || "Unknown error")}`, resourceType, logicalId);
45796
+ if (status === "FAIL") {
45797
+ const errorMessage = maskerOrIdentity(maskSecrets)(result.Operation?.ErrorMessage || "Unknown error");
45798
+ throw new ProvisioningError(`Operation failed for ${logicalId}: ${errorMessage}`, resourceType, logicalId);
45799
+ }
45434
45800
  await new Promise((resolve) => setTimeout(resolve, delay));
45435
45801
  delay = Math.min(delay * 2, 1e4);
45436
45802
  }
@@ -45596,7 +45962,8 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
45596
45962
  /** Best-effort tag fetch via `ListTagsForResource(ResourceARN)`. */
45597
45963
  async attachTags(result, arn) {
45598
45964
  try {
45599
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$14({ ResourceARN: arn }))).Tags);
45965
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$14({ ResourceARN: arn }));
45966
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
45600
45967
  } catch (err) {
45601
45968
  this.logger.debug(`ServiceDiscovery ListTagsForResource(${arn}) failed: ${err instanceof Error ? err.message : String(err)}`);
45602
45969
  }
@@ -45844,7 +46211,8 @@ var AppSyncProvider = class {
45844
46211
  * at most one `GetCallerIdentity` per deploy.
45845
46212
  */
45846
46213
  async buildAppSyncArn(suffix, regionOverride) {
45847
- const accountInfo = await getAccountInfo(regionOverride ?? this.providerRegion ?? await this.getClient().config.region());
46214
+ const region = regionOverride ?? this.providerRegion ?? await this.getClient().config.region();
46215
+ const accountInfo = await getAccountInfo(region);
45848
46216
  if (accountInfo.fabricated) throw new Error(`cannot determine the AWS account (STS is unreachable, and the resolved account id is a placeholder), so an ARN for ${suffix} would be fabricated`);
45849
46217
  const { partition } = derivePartitionAndUrlSuffix(accountInfo.region);
45850
46218
  return `arn:${partition}:appsync:${canonicalizeRegion(accountInfo.region)}:${accountInfo.accountId}:${suffix}`;
@@ -46880,7 +47248,8 @@ var AppSyncProvider = class {
46880
47248
  this.logger.debug(`Successfully deleted GraphQL API ${logicalId}`);
46881
47249
  } catch (error) {
46882
47250
  if (this.isNotFoundError(error)) {
46883
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
47251
+ const clientRegion = await this.getClient().config.region();
47252
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
46884
47253
  this.logger.debug(`GraphQL API ${physicalId} does not exist, skipping deletion`);
46885
47254
  return;
46886
47255
  }
@@ -46972,7 +47341,8 @@ var AppSyncProvider = class {
46972
47341
  this.logger.debug(`Successfully deleted DataSource ${logicalId}`);
46973
47342
  } catch (error) {
46974
47343
  if (this.isNotFoundError(error)) {
46975
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
47344
+ const clientRegion = await this.getClient().config.region();
47345
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
46976
47346
  this.logger.debug(`DataSource ${physicalId} does not exist, skipping deletion`);
46977
47347
  return;
46978
47348
  }
@@ -47040,7 +47410,8 @@ var AppSyncProvider = class {
47040
47410
  this.logger.debug(`Successfully deleted Resolver ${logicalId}`);
47041
47411
  } catch (error) {
47042
47412
  if (this.isNotFoundError(error)) {
47043
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
47413
+ const clientRegion = await this.getClient().config.region();
47414
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
47044
47415
  this.logger.debug(`Resolver ${physicalId} does not exist, skipping deletion`);
47045
47416
  return;
47046
47417
  }
@@ -47097,7 +47468,8 @@ var AppSyncProvider = class {
47097
47468
  this.logger.debug(`Successfully deleted ApiKey ${logicalId}`);
47098
47469
  } catch (error) {
47099
47470
  if (this.isNotFoundError(error)) {
47100
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
47471
+ const clientRegion = await this.getClient().config.region();
47472
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
47101
47473
  this.logger.debug(`ApiKey ${physicalId} does not exist, skipping deletion`);
47102
47474
  return;
47103
47475
  }
@@ -47756,7 +48128,8 @@ var GlueProvider = class {
47756
48128
  this.logger.debug(`Successfully deleted Glue Database ${logicalId}`);
47757
48129
  } catch (error) {
47758
48130
  if (error instanceof EntityNotFoundException) {
47759
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
48131
+ const clientRegion = await this.getClient().config.region();
48132
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
47760
48133
  logCatalogScopedDeleteSkip(this.logger, target, "Database", logicalId, physicalId);
47761
48134
  return;
47762
48135
  }
@@ -47859,7 +48232,8 @@ var GlueProvider = class {
47859
48232
  this.logger.debug(`Successfully deleted Glue Table ${logicalId}`);
47860
48233
  } catch (error) {
47861
48234
  if (error instanceof EntityNotFoundException) {
47862
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
48235
+ const clientRegion = await this.getClient().config.region();
48236
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
47863
48237
  logCatalogScopedDeleteSkip(this.logger, target, "Table", logicalId, physicalId);
47864
48238
  return;
47865
48239
  }
@@ -48470,10 +48844,7 @@ var GlueProvider = class {
48470
48844
  case "pipe-in-name":
48471
48845
  this.logger.warn(`AWS::Glue::Table ${input.logicalId}: cannot be imported because a name contains '|', which cdkd currently uses as the separator in this type's physical id (<databaseName>|<tableName>). Adopting it would record an id cdkd cannot decode back to the same table. CloudFormation manages such a table fine; this is a cdkd limitation tracked in https://github.com/go-to-k/cdkd/issues/1672. Until it is lifted, rename the table or database to omit '|', or manage this resource outside cdkd.`);
48472
48846
  break;
48473
- case "unpairable":
48474
- this.logger.warn(`AWS::Glue::Table ${input.logicalId}: cannot resolve a database for physical id '${input.knownPhysicalId}'. ` + (input.knownPhysicalId === "" ? `The supplied physical id is empty. ` : "The template's DatabaseName is absent or is an unresolved intrinsic, so the bare table name cannot be paired. ") + `Pass the composite form instead: --resource '${input.logicalId}=<databaseName>|<tableName>' (quote it — '|' is a shell pipe).`);
48475
- break;
48476
- case "unidentified": break;
48847
+ case "unpairable": this.logger.warn(`AWS::Glue::Table ${input.logicalId}: cannot resolve a database for physical id '${input.knownPhysicalId}'. ` + (input.knownPhysicalId === "" ? `The supplied physical id is empty. ` : "The template's DatabaseName is absent or is an unresolved intrinsic, so the bare table name cannot be paired. ") + `Pass the composite form instead: --resource '${input.logicalId}=<databaseName>|<tableName>' (quote it — '|' is a shell pipe).`);
48477
48848
  }
48478
48849
  return null;
48479
48850
  }
@@ -48578,7 +48949,8 @@ var GlueWorkflowProvider = class {
48578
48949
  this.logger.debug(`Successfully deleted Glue Workflow ${logicalId}`);
48579
48950
  } catch (error) {
48580
48951
  if (error instanceof EntityNotFoundException) {
48581
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
48952
+ const clientRegion = await this.getClient().config.region();
48953
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
48582
48954
  this.logger.debug(`Glue Workflow ${physicalId} does not exist, skipping deletion`);
48583
48955
  return;
48584
48956
  }
@@ -48622,7 +48994,8 @@ var GlueWorkflowProvider = class {
48622
48994
  const arn = await this.buildWorkflowArn(physicalId);
48623
48995
  let tags = [];
48624
48996
  try {
48625
- tags = normalizeAwsTagsToCfn((await this.getClient().send(new GetTagsCommand({ ResourceArn: arn }))).Tags);
48997
+ const tagResp = await this.getClient().send(new GetTagsCommand({ ResourceArn: arn }));
48998
+ tags = normalizeAwsTagsToCfn(tagResp.Tags);
48626
48999
  } catch (err) {
48627
49000
  this.logger.debug(`GetTags failed for Glue Workflow ${physicalId}: ${err instanceof Error ? err.message : String(err)}`);
48628
49001
  }
@@ -48716,7 +49089,8 @@ var GlueSecurityConfigurationProvider = class {
48716
49089
  this.logger.debug(`Successfully deleted Glue SecurityConfiguration ${logicalId}`);
48717
49090
  } catch (error) {
48718
49091
  if (error instanceof EntityNotFoundException) {
48719
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
49092
+ const clientRegion = await this.getClient().config.region();
49093
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
48720
49094
  this.logger.debug(`Glue SecurityConfiguration ${physicalId} does not exist, skipping deletion`);
48721
49095
  return;
48722
49096
  }
@@ -48987,7 +49361,8 @@ async function resolveAccountId(stsClient) {
48987
49361
  async function fetchGlueTags(client, stsClient, resource, name, accountId, logger) {
48988
49362
  try {
48989
49363
  const arn = await buildGlueResourceArn(client, stsClient, resource, name, accountId);
48990
- return normalizeAwsTagsToCfn((await client.send(new GetTagsCommand({ ResourceArn: arn }))).Tags);
49364
+ const resp = await client.send(new GetTagsCommand({ ResourceArn: arn }));
49365
+ return normalizeAwsTagsToCfn(resp.Tags);
48991
49366
  } catch (err) {
48992
49367
  logger.debug(`GetTags failed for ${resource}/${name}: ${err instanceof Error ? err.message : String(err)}`);
48993
49368
  return [];
@@ -49111,7 +49486,8 @@ var GlueJobProvider = class {
49111
49486
  await this.getClient().send(new DeleteJobCommand({ JobName: physicalId }));
49112
49487
  } catch (error) {
49113
49488
  if (error instanceof EntityNotFoundException) {
49114
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
49489
+ const clientRegion = await this.getClient().config.region();
49490
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
49115
49491
  this.logger.debug(`Glue Job ${physicalId} does not exist, skipping deletion`);
49116
49492
  return;
49117
49493
  }
@@ -49495,7 +49871,8 @@ var GlueCrawlerProvider = class {
49495
49871
  await this.getClient().send(new DeleteCrawlerCommand({ Name: physicalId }));
49496
49872
  } catch (error) {
49497
49873
  if (error instanceof EntityNotFoundException) {
49498
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
49874
+ const clientRegion = await this.getClient().config.region();
49875
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
49499
49876
  this.logger.debug(`Glue Crawler ${physicalId} does not exist, skipping deletion`);
49500
49877
  return;
49501
49878
  }
@@ -49792,7 +50169,8 @@ var GlueConnectionProvider = class {
49792
50169
  }));
49793
50170
  } catch (error) {
49794
50171
  if (error instanceof EntityNotFoundException) {
49795
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
50172
+ const clientRegion = await this.getClient().config.region();
50173
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
49796
50174
  logCatalogScopedDeleteSkip(this.logger, target, "Connection", logicalId, physicalId);
49797
50175
  return;
49798
50176
  }
@@ -50022,7 +50400,8 @@ var GlueTriggerProvider = class {
50022
50400
  await this.getClient().send(new DeleteTriggerCommand({ Name: physicalId }));
50023
50401
  } catch (error) {
50024
50402
  if (error instanceof EntityNotFoundException) {
50025
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
50403
+ const clientRegion = await this.getClient().config.region();
50404
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
50026
50405
  this.logger.debug(`Glue Trigger ${physicalId} does not exist, skipping deletion`);
50027
50406
  return;
50028
50407
  }
@@ -50214,24 +50593,28 @@ var KMSProvider = class {
50214
50593
  }));
50215
50594
  }
50216
50595
  const newEnableKeyRotation = properties["EnableKeyRotation"];
50217
- if (newEnableKeyRotation !== previousProperties["EnableKeyRotation"]) if (newEnableKeyRotation) {
50218
- const rotationPeriodInDays = properties["RotationPeriodInDays"];
50219
- this.logger.debug(`Enabling key rotation for KMS Key ${logicalId}`);
50220
- await this.getClient().send(new EnableKeyRotationCommand({
50221
- KeyId: physicalId,
50222
- ...rotationPeriodInDays !== void 0 && { RotationPeriodInDays: rotationPeriodInDays }
50223
- }));
50224
- } else {
50225
- this.logger.debug(`Disabling key rotation for KMS Key ${logicalId}`);
50226
- await this.getClient().send(new DisableKeyRotationCommand({ KeyId: physicalId }));
50596
+ if (newEnableKeyRotation !== previousProperties["EnableKeyRotation"]) {
50597
+ if (newEnableKeyRotation) {
50598
+ const rotationPeriodInDays = properties["RotationPeriodInDays"];
50599
+ this.logger.debug(`Enabling key rotation for KMS Key ${logicalId}`);
50600
+ await this.getClient().send(new EnableKeyRotationCommand({
50601
+ KeyId: physicalId,
50602
+ ...rotationPeriodInDays !== void 0 && { RotationPeriodInDays: rotationPeriodInDays }
50603
+ }));
50604
+ } else {
50605
+ this.logger.debug(`Disabling key rotation for KMS Key ${logicalId}`);
50606
+ await this.getClient().send(new DisableKeyRotationCommand({ KeyId: physicalId }));
50607
+ }
50227
50608
  }
50228
50609
  const newEnabled = properties["Enabled"];
50229
- if (newEnabled !== previousProperties["Enabled"]) if (newEnabled === false) {
50230
- this.logger.debug(`Disabling KMS Key ${logicalId}`);
50231
- await this.getClient().send(new DisableKeyCommand({ KeyId: physicalId }));
50232
- } else {
50233
- this.logger.debug(`Enabling KMS Key ${logicalId}`);
50234
- await this.getClient().send(new EnableKeyCommand({ KeyId: physicalId }));
50610
+ if (newEnabled !== previousProperties["Enabled"]) {
50611
+ if (newEnabled === false) {
50612
+ this.logger.debug(`Disabling KMS Key ${logicalId}`);
50613
+ await this.getClient().send(new DisableKeyCommand({ KeyId: physicalId }));
50614
+ } else {
50615
+ this.logger.debug(`Enabling KMS Key ${logicalId}`);
50616
+ await this.getClient().send(new EnableKeyCommand({ KeyId: physicalId }));
50617
+ }
50235
50618
  }
50236
50619
  await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
50237
50620
  const newKeyPolicy = properties["KeyPolicy"];
@@ -50266,7 +50649,8 @@ var KMSProvider = class {
50266
50649
  this.logger.debug(`Successfully scheduled deletion for KMS Key ${logicalId}`);
50267
50650
  } catch (error) {
50268
50651
  if (error instanceof NotFoundException$2) {
50269
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
50652
+ const clientRegion = await this.getClient().config.region();
50653
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
50270
50654
  this.logger.debug(`KMS Key ${physicalId} does not exist, skipping deletion`);
50271
50655
  return;
50272
50656
  }
@@ -50356,7 +50740,8 @@ var KMSProvider = class {
50356
50740
  this.logger.debug(`Successfully deleted KMS Alias ${logicalId}`);
50357
50741
  } catch (error) {
50358
50742
  if (error instanceof NotFoundException$2) {
50359
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
50743
+ const clientRegion = await this.getClient().config.region();
50744
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
50360
50745
  this.logger.debug(`KMS Alias ${physicalId} does not exist, skipping deletion`);
50361
50746
  return;
50362
50747
  }
@@ -50433,7 +50818,8 @@ var KMSProvider = class {
50433
50818
  if (err instanceof NotFoundException$2) return void 0;
50434
50819
  }
50435
50820
  try {
50436
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListResourceTagsCommand({ KeyId: md.KeyId }))).Tags);
50821
+ const tagsResp = await this.getClient().send(new ListResourceTagsCommand({ KeyId: md.KeyId }));
50822
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
50437
50823
  } catch (err) {
50438
50824
  if (err instanceof NotFoundException$2) return void 0;
50439
50825
  }
@@ -50619,7 +51005,8 @@ var BudgetsBudgetProvider = class {
50619
51005
  * unchanged byte for byte.
50620
51006
  */
50621
51007
  async budgetArn(accountId, budgetName) {
50622
- const { partition } = derivePartitionAndUrlSuffix(await this.getClient().config.region() ?? "");
51008
+ const region = await this.getClient().config.region();
51009
+ const { partition } = derivePartitionAndUrlSuffix(region ?? "");
50623
51010
  return `arn:${partition}:budgets::${accountId}:budget/${budgetName}`;
50624
51011
  }
50625
51012
  /**
@@ -50953,7 +51340,8 @@ var BudgetsBudgetProvider = class {
50953
51340
  this.logger.debug(`Successfully deleted budget ${logicalId}`);
50954
51341
  } catch (error) {
50955
51342
  if (error instanceof NotFoundException$5) {
50956
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
51343
+ const clientRegion = await this.getClient().config.region();
51344
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
50957
51345
  this.logger.debug(`Budget ${physicalId} does not exist, skipping deletion`);
50958
51346
  return;
50959
51347
  }
@@ -51196,11 +51584,15 @@ var KinesisStreamProvider = class {
51196
51584
  this.logger.debug(`Creating Kinesis stream ${logicalId}`);
51197
51585
  const streamName = properties["Name"] || generateResourceName(logicalId, { maxLength: 128 });
51198
51586
  const desiredRead = readShardLevelMetrics(properties["DesiredShardLevelMetrics"], mask);
51199
- if (desiredRead.kind === "unusable") if (context?.replayingState === true) warn(`${desiredRead.reason}. Replaying a state record, so the shard-level metrics are left unset on ${streamName} rather than refusing the restore`);
51200
- else throw new ProvisioningError(desiredRead.reason, resourceType, logicalId, streamName);
51587
+ if (desiredRead.kind === "unusable") {
51588
+ if (context?.replayingState === true) warn(`${desiredRead.reason}. Replaying a state record, so the shard-level metrics are left unset on ${streamName} rather than refusing the restore`);
51589
+ else throw new ProvisioningError(desiredRead.reason, resourceType, logicalId, streamName);
51590
+ }
51201
51591
  const maxRecordRead = readMaxRecordSize(properties["MaxRecordSizeInKiB"], mask);
51202
- if (maxRecordRead.kind === "unusable") if (context?.replayingState === true) warn(`${maxRecordRead.reason}. Replaying a state record, so ${streamName} is created with AWS's default maximum record size rather than refusing the restore`);
51203
- else throw new ProvisioningError(maxRecordRead.reason, resourceType, logicalId, streamName);
51592
+ if (maxRecordRead.kind === "unusable") {
51593
+ if (context?.replayingState === true) warn(`${maxRecordRead.reason}. Replaying a state record, so ${streamName} is created with AWS's default maximum record size rather than refusing the restore`);
51594
+ else throw new ProvisioningError(maxRecordRead.reason, resourceType, logicalId, streamName);
51595
+ }
51204
51596
  try {
51205
51597
  const streamModeDetails = properties["StreamModeDetails"];
51206
51598
  const streamMode = readConfigString(streamModeDetails, "StreamMode", "PROVISIONED", "AWS::Kinesis::Stream StreamModeDetails");
@@ -51411,7 +51803,8 @@ var KinesisStreamProvider = class {
51411
51803
  this.logger.debug(`Successfully deleted Kinesis stream ${logicalId}`);
51412
51804
  } catch (error) {
51413
51805
  if (error instanceof ResourceNotFoundException$10) {
51414
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
51806
+ const clientRegion = await this.getClient().config.region();
51807
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
51415
51808
  this.logger.debug(`Kinesis stream ${physicalId} does not exist, skipping deletion`);
51416
51809
  return;
51417
51810
  }
@@ -51557,7 +51950,8 @@ var KinesisStreamProvider = class {
51557
51950
  throw err;
51558
51951
  }
51559
51952
  try {
51560
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForStreamCommand({ StreamName: physicalId }))).Tags);
51953
+ const tagsResp = await this.getClient().send(new ListTagsForStreamCommand({ StreamName: physicalId }));
51954
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
51561
51955
  } catch (err) {
51562
51956
  if (err instanceof ResourceNotFoundException$10) return void 0;
51563
51957
  this.logger.debug(`Kinesis ListTagsForStream(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -51765,7 +52159,8 @@ var KinesisStreamConsumerProvider = class {
51765
52159
  this.logger.debug(`Successfully deregistered Kinesis stream consumer ${logicalId}`);
51766
52160
  } catch (error) {
51767
52161
  if (error instanceof ResourceNotFoundException$10) {
51768
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
52162
+ const clientRegion = await this.getClient().config.region();
52163
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
51769
52164
  this.logger.debug(`Kinesis stream consumer ${physicalId} not found, skipping`);
51770
52165
  return;
51771
52166
  }
@@ -51818,7 +52213,8 @@ var KinesisStreamConsumerProvider = class {
51818
52213
  if (desc.ConsumerName !== void 0) result["ConsumerName"] = desc.ConsumerName;
51819
52214
  if (desc.StreamARN !== void 0) result["StreamARN"] = desc.StreamARN;
51820
52215
  try {
51821
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$15({ ResourceARN: physicalId }))).Tags);
52216
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$15({ ResourceARN: physicalId }));
52217
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
51822
52218
  } catch (err) {
51823
52219
  if (err instanceof ResourceNotFoundException$10) return void 0;
51824
52220
  this.logger.debug(`ListTagsForResource(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -52114,7 +52510,8 @@ var SchedulerScheduleProvider = class {
52114
52510
  this.logger.debug(`Deleted Schedule ${logicalId}`);
52115
52511
  } catch (error) {
52116
52512
  if (error instanceof ResourceNotFoundException$11) {
52117
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
52513
+ const clientRegion = await this.getClient().config.region();
52514
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
52118
52515
  this.logger.debug(`Schedule ${logicalId} already deleted (not found), treating as success`);
52119
52516
  return;
52120
52517
  }
@@ -52508,7 +52905,8 @@ var EFSProvider = class {
52508
52905
  this.logger.debug(`Successfully deleted EFS FileSystem ${logicalId}`);
52509
52906
  } catch (error) {
52510
52907
  if (error instanceof FileSystemNotFound) {
52511
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
52908
+ const clientRegion = await this.getClient().config.region();
52909
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
52512
52910
  this.logger.debug(`EFS FileSystem ${physicalId} does not exist, skipping deletion`);
52513
52911
  return;
52514
52912
  }
@@ -52574,7 +52972,8 @@ var EFSProvider = class {
52574
52972
  this.logger.debug(`Successfully deleted EFS MountTarget ${logicalId}`);
52575
52973
  } catch (error) {
52576
52974
  if (error instanceof MountTargetNotFound) {
52577
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
52975
+ const clientRegion = await this.getClient().config.region();
52976
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
52578
52977
  this.logger.debug(`EFS MountTarget ${physicalId} does not exist, skipping deletion`);
52579
52978
  return;
52580
52979
  }
@@ -52746,7 +53145,8 @@ var EFSProvider = class {
52746
53145
  this.logger.debug(`Successfully deleted EFS AccessPoint ${logicalId}`);
52747
53146
  } catch (error) {
52748
53147
  if (error instanceof AccessPointNotFound) {
52749
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
53148
+ const clientRegion = await this.getClient().config.region();
53149
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
52750
53150
  this.logger.debug(`EFS AccessPoint ${physicalId} does not exist, skipping deletion`);
52751
53151
  return;
52752
53152
  }
@@ -52948,7 +53348,7 @@ var EFSProvider = class {
52948
53348
  * storage-capacity growth and deletes can take substantially longer —
52949
53349
  * mirror the Custom Resource provider's 1-hour ceiling.
52950
53350
  */
52951
- const DEFAULT_MAX_WAIT_MS$3 = 3600 * 1e3;
53351
+ const DEFAULT_MAX_WAIT_MS$3 = 36e5;
52952
53352
  const DEFAULT_POLL_INTERVAL_MS$3 = 15e3;
52953
53353
  /**
52954
53354
  * Lustre sub-properties that `UpdateFileSystem` accepts (the mutable
@@ -53672,7 +54072,8 @@ var FSxFileSystemProvider = class {
53672
54072
  await this.getClient().send(new DeleteFileSystemCommand$1({ FileSystemId: physicalId }));
53673
54073
  } catch (error) {
53674
54074
  if (error instanceof FileSystemNotFound$1) {
53675
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
54075
+ const clientRegion = await this.getClient().config.region();
54076
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
53676
54077
  this.logger.debug(`FSx FileSystem ${physicalId} does not exist, skipping deletion`);
53677
54078
  return;
53678
54079
  }
@@ -53714,7 +54115,10 @@ var FSxFileSystemProvider = class {
53714
54115
  const fs = await this.describeForPoll(fileSystemId, transientState);
53715
54116
  const lifecycle = fs?.Lifecycle;
53716
54117
  if (fs && lifecycle === "AVAILABLE") return fs;
53717
- if (lifecycle === "FAILED" || lifecycle === "MISCONFIGURED") throw new ProvisioningError(`FSx FileSystem ${fileSystemId} entered lifecycle state ${lifecycle}: ${fs?.FailureDetails?.Message ?? "no failure details reported"}`, resourceType, logicalId, fileSystemId);
54118
+ if (lifecycle === "FAILED" || lifecycle === "MISCONFIGURED") {
54119
+ const detail = fs?.FailureDetails?.Message ?? "no failure details reported";
54120
+ throw new ProvisioningError(`FSx FileSystem ${fileSystemId} entered lifecycle state ${lifecycle}: ${detail}`, resourceType, logicalId, fileSystemId);
54121
+ }
53718
54122
  this.logger.debug(`FSx FileSystem ${fileSystemId} state: ${lifecycle ?? "unknown"}, waiting...`);
53719
54123
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
53720
54124
  }
@@ -53753,7 +54157,10 @@ var FSxFileSystemProvider = class {
53753
54157
  const updateActions = (fs.AdministrativeActions ?? []).filter((a) => a.AdministrativeActionType === "FILE_SYSTEM_UPDATE" && (a.RequestTime === void 0 || a.RequestTime.getTime() >= actionThresholdMs));
53754
54158
  if (updateActions.length > 0) seenTrackedAction = true;
53755
54159
  const failed = updateActions.find((a) => a.Status === "FAILED");
53756
- if (failed) throw new ProvisioningError(`FSx FileSystem ${fileSystemId} update failed (FILE_SYSTEM_UPDATE administrative action): ${failed.FailureDetails?.Message ?? "no failure details reported"}`, resourceType, logicalId, fileSystemId);
54160
+ if (failed) {
54161
+ const detail = failed.FailureDetails?.Message ?? "no failure details reported";
54162
+ throw new ProvisioningError(`FSx FileSystem ${fileSystemId} update failed (FILE_SYSTEM_UPDATE administrative action): ${detail}`, resourceType, logicalId, fileSystemId);
54163
+ }
53757
54164
  if (!updateActions.some((a) => a.Status === "PENDING" || a.Status === "IN_PROGRESS") && (!requireActionObservation || seenTrackedAction) && fs.Lifecycle === "AVAILABLE") return fs;
53758
54165
  this.logger.debug(`FSx FileSystem ${fileSystemId} update in progress (lifecycle: ${fs.Lifecycle ?? "unknown"}), waiting...`);
53759
54166
  }
@@ -53780,7 +54187,10 @@ var FSxFileSystemProvider = class {
53780
54187
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
53781
54188
  continue;
53782
54189
  }
53783
- if (fs.Lifecycle === "FAILED") throw new ProvisioningError(`FSx FileSystem ${fileSystemId} entered lifecycle state FAILED during deletion: ${fs.FailureDetails?.Message ?? "no failure details reported"}`, resourceType, logicalId, fileSystemId);
54190
+ if (fs.Lifecycle === "FAILED") {
54191
+ const detail = fs.FailureDetails?.Message ?? "no failure details reported";
54192
+ throw new ProvisioningError(`FSx FileSystem ${fileSystemId} entered lifecycle state FAILED during deletion: ${detail}`, resourceType, logicalId, fileSystemId);
54193
+ }
53784
54194
  this.logger.debug(`FSx FileSystem ${fileSystemId} state: ${fs.Lifecycle ?? "unknown"}, waiting for deletion...`);
53785
54195
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
53786
54196
  }
@@ -54041,9 +54451,7 @@ var FSxFileSystemProvider = class {
54041
54451
  case "OpenZFSConfiguration":
54042
54452
  result[variantKey] = this.readOpenZFSConfiguration(fs.OpenZFSConfiguration ?? {});
54043
54453
  break;
54044
- default:
54045
- this.logger.warn(`FSx FileSystem ${physicalId} has FileSystemType '${fs.FileSystemType ?? "(unset)"}', which cdkd does not recognize (expected one of ${Object.keys(VARIANT_CONFIG_KEY).join(" / ")}). Its variant configuration is NOT included in the drift snapshot, so drift in that block cannot be detected. Please report this at https://github.com/go-to-k/cdkd/issues`);
54046
- break;
54454
+ default: this.logger.warn(`FSx FileSystem ${physicalId} has FileSystemType '${fs.FileSystemType ?? "(unset)"}', which cdkd does not recognize (expected one of ${Object.keys(VARIANT_CONFIG_KEY).join(" / ")}). Its variant configuration is NOT included in the drift snapshot, so drift in that block cannot be detected. Please report this at https://github.com/go-to-k/cdkd/issues`);
54047
54455
  }
54048
54456
  return result;
54049
54457
  }
@@ -54159,7 +54567,7 @@ function toSdkInstanceTypeConfigs(value) {
54159
54567
  * FSx providers' 1-hour ceiling so the slowest realistic create/terminate
54160
54568
  * still fits inside the per-resource deadline.
54161
54569
  */
54162
- const DEFAULT_MAX_WAIT_MS$2 = 3600 * 1e3;
54570
+ const DEFAULT_MAX_WAIT_MS$2 = 36e5;
54163
54571
  const DEFAULT_POLL_INTERVAL_MS$2 = 15e3;
54164
54572
  /** Cluster states that mean "create succeeded, the cluster is up". */
54165
54573
  const CREATE_READY_STATES = /* @__PURE__ */ new Set(["WAITING", "RUNNING"]);
@@ -54577,7 +54985,8 @@ var EMRClusterProvider = class {
54577
54985
  current = (await this.getClient().send(new DescribeClusterCommand({ ClusterId: physicalId }))).Cluster;
54578
54986
  } catch (error) {
54579
54987
  if (error instanceof InvalidRequestException) {
54580
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
54988
+ const clientRegion = await this.getClient().config.region();
54989
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
54581
54990
  this.logger.debug(`EMR Cluster ${physicalId} does not exist, skipping deletion`);
54582
54991
  return;
54583
54992
  }
@@ -54636,7 +55045,10 @@ var EMRClusterProvider = class {
54636
55045
  const cluster = await this.describeForPoll(clusterId, transientState);
54637
55046
  const state = cluster?.Status?.State;
54638
55047
  if (cluster && state && CREATE_READY_STATES.has(state)) return cluster;
54639
- if (state && TERMINAL_STATES.has(state)) throw new ProvisioningError(`EMR Cluster ${clusterId} entered terminal state ${state} during creation: ${cluster?.Status?.StateChangeReason?.Message ?? "no state-change reason reported"}`, resourceType, logicalId, clusterId);
55048
+ if (state && TERMINAL_STATES.has(state)) {
55049
+ const reason = cluster?.Status?.StateChangeReason?.Message ?? "no state-change reason reported";
55050
+ throw new ProvisioningError(`EMR Cluster ${clusterId} entered terminal state ${state} during creation: ${reason}`, resourceType, logicalId, clusterId);
55051
+ }
54640
55052
  this.logger.debug(`EMR Cluster ${clusterId} state: ${state ?? "unknown"}, waiting...`);
54641
55053
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
54642
55054
  }
@@ -54888,9 +55300,7 @@ var EMRClusterProvider = class {
54888
55300
  case "CORE":
54889
55301
  instances["CoreInstanceGroup"] = cfn;
54890
55302
  break;
54891
- case "TASK":
54892
- taskGroups.push(cfn);
54893
- break;
55303
+ case "TASK": taskGroups.push(cfn);
54894
55304
  }
54895
55305
  }
54896
55306
  if (taskGroups.length > 0) instances["TaskInstanceGroups"] = taskGroups;
@@ -54904,9 +55314,7 @@ var EMRClusterProvider = class {
54904
55314
  case "CORE":
54905
55315
  instances["CoreInstanceFleet"] = cfn;
54906
55316
  break;
54907
- case "TASK":
54908
- taskFleets.push(cfn);
54909
- break;
55317
+ case "TASK": taskFleets.push(cfn);
54910
55318
  }
54911
55319
  }
54912
55320
  if (taskFleets.length > 0) instances["TaskInstanceFleets"] = taskFleets;
@@ -54951,7 +55359,7 @@ var EMRClusterProvider = class {
54951
55359
  * EMR Cluster provider's 1-hour ceiling so the slowest realistic add still
54952
55360
  * fits inside the per-resource deadline.
54953
55361
  */
54954
- const DEFAULT_MAX_WAIT_MS$1 = 3600 * 1e3;
55362
+ const DEFAULT_MAX_WAIT_MS$1 = 36e5;
54955
55363
  const DEFAULT_POLL_INTERVAL_MS$1 = 15e3;
54956
55364
  /** Instance-group state that means "the group is up and idle/serving". */
54957
55365
  const READY_STATES$1 = /* @__PURE__ */ new Set(["RUNNING"]);
@@ -55165,7 +55573,8 @@ var EMRInstanceGroupConfigProvider = class {
55165
55573
  this.logger.debug(`Scaled EMR TASK instance group ${physicalId} to 0 to release its instances (no standalone delete API exists)`);
55166
55574
  } catch (error) {
55167
55575
  if (error instanceof InvalidRequestException) {
55168
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
55576
+ const clientRegion = await this.getClient().config.region();
55577
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
55169
55578
  this.logger.debug(`EMR instance group ${physicalId} scale-to-0 skipped (cluster/group gone): ${error.message}`);
55170
55579
  } else this.logger.warn(`Best-effort scale-to-0 of EMR instance group ${physicalId} failed: ${error instanceof Error ? error.message : String(error)} — its instances are released when the parent cluster terminates`);
55171
55580
  }
@@ -55219,7 +55628,10 @@ var EMRInstanceGroupConfigProvider = class {
55219
55628
  }
55220
55629
  if (state && READY_STATES$1.has(state) && group?.RunningInstanceCount === expectedCount) return;
55221
55630
  const enforceFailedStates = !toleratesStaleFailedState || hasLeftInitialState;
55222
- if (state && enforceFailedStates && FAILED_STATES$1.has(state)) throw new ProvisioningError(`EMR instance group ${groupId} entered failed state ${state}: ${group?.Status?.StateChangeReason?.Message ?? "no state-change reason reported"}`, resourceType, logicalId, groupId);
55631
+ if (state && enforceFailedStates && FAILED_STATES$1.has(state)) {
55632
+ const reason = group?.Status?.StateChangeReason?.Message ?? "no state-change reason reported";
55633
+ throw new ProvisioningError(`EMR instance group ${groupId} entered failed state ${state}: ${reason}`, resourceType, logicalId, groupId);
55634
+ }
55223
55635
  if (state && !enforceFailedStates && FAILED_STATES$1.has(state)) this.logger.debug(`EMR instance group ${groupId} still reads ${state} from before the resize; waiting for it to leave that state before treating it as failed`);
55224
55636
  this.logger.debug(`EMR instance group ${groupId} state: ${state ?? "unknown"}, running ${group?.RunningInstanceCount ?? "?"}/${expectedCount}, waiting...`);
55225
55637
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
@@ -55271,7 +55683,7 @@ var EMRInstanceGroupConfigProvider = class {
55271
55683
  * EMR Cluster provider's 1-hour ceiling so the slowest realistic add still
55272
55684
  * fits inside the per-resource deadline.
55273
55685
  */
55274
- const DEFAULT_MAX_WAIT_MS = 3600 * 1e3;
55686
+ const DEFAULT_MAX_WAIT_MS = 36e5;
55275
55687
  const DEFAULT_POLL_INTERVAL_MS = 15e3;
55276
55688
  /** Instance-fleet state that means "the fleet is up and idle/serving". */
55277
55689
  const READY_STATES = /* @__PURE__ */ new Set(["RUNNING"]);
@@ -55475,7 +55887,8 @@ var EMRInstanceFleetConfigProvider = class {
55475
55887
  this.logger.debug(`Scaled EMR TASK instance fleet ${physicalId} to 0 to release its instances (no standalone delete API exists)`);
55476
55888
  } catch (error) {
55477
55889
  if (error instanceof InvalidRequestException) {
55478
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
55890
+ const clientRegion = await this.getClient().config.region();
55891
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
55479
55892
  this.logger.debug(`EMR instance fleet ${physicalId} scale-to-0 skipped (cluster/fleet gone): ${error.message}`);
55480
55893
  } else this.logger.warn(`Best-effort scale-to-0 of EMR instance fleet ${physicalId} failed: ${error instanceof Error ? error.message : String(error)} — its instances are released when the parent cluster terminates`);
55481
55894
  }
@@ -55569,7 +55982,10 @@ var EMRInstanceFleetConfigProvider = class {
55569
55982
  }
55570
55983
  if (state && READY_STATES.has(state) && capacityReady) return;
55571
55984
  const enforceFailedStates = !toleratesStaleFailedState || hasLeftInitialState;
55572
- if (state && enforceFailedStates && FAILED_STATES.has(state)) throw new ProvisioningError(`EMR instance fleet ${fleetId} entered failed state ${state}: ${fleet?.Status?.StateChangeReason?.Message ?? "no state-change reason reported"}`, resourceType, logicalId, fleetId);
55985
+ if (state && enforceFailedStates && FAILED_STATES.has(state)) {
55986
+ const reason = fleet?.Status?.StateChangeReason?.Message ?? "no state-change reason reported";
55987
+ throw new ProvisioningError(`EMR instance fleet ${fleetId} entered failed state ${state}: ${reason}`, resourceType, logicalId, fleetId);
55988
+ }
55573
55989
  if (state && !enforceFailedStates && FAILED_STATES.has(state)) this.logger.debug(`EMR instance fleet ${fleetId} still reads ${state} from before the resize; waiting for it to leave that state before treating it as failed`);
55574
55990
  this.logger.debug(`EMR instance fleet ${fleetId} state: ${state ?? "unknown"}, provisioned ${onDemand}/${target.onDemand} on-demand + ${spot}/${target.spot} spot (${provisioned} total), waiting...`);
55575
55991
  await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
@@ -56085,7 +56501,8 @@ var FirehoseProvider = class {
56085
56501
  this.logger.debug(`Successfully deleted Firehose delivery stream ${logicalId}`);
56086
56502
  } catch (error) {
56087
56503
  if (error instanceof ResourceNotFoundException$12) {
56088
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
56504
+ const clientRegion = await this.getClient().config.region();
56505
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
56089
56506
  this.logger.debug(`Firehose delivery stream ${physicalId} does not exist, skipping deletion`);
56090
56507
  return;
56091
56508
  }
@@ -56650,7 +57067,8 @@ var FirehoseProvider = class {
56650
57067
  if (enc?.KeyType !== void 0) encOut["KeyType"] = enc.KeyType;
56651
57068
  result["DeliveryStreamEncryptionConfigurationInput"] = encOut;
56652
57069
  try {
56653
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForDeliveryStreamCommand({ DeliveryStreamName: physicalId }))).Tags);
57070
+ const tagsResp = await this.getClient().send(new ListTagsForDeliveryStreamCommand({ DeliveryStreamName: physicalId }));
57071
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
56654
57072
  } catch (err) {
56655
57073
  if (err instanceof ResourceNotFoundException$12) return void 0;
56656
57074
  this.logger.debug(`Firehose ListTagsForDeliveryStream(${physicalId}) failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -57145,12 +57563,14 @@ var CloudTrailProvider = class {
57145
57563
  }));
57146
57564
  }
57147
57565
  const oldIsLogging = previousProperties["IsLogging"];
57148
- if (isLogging !== void 0 && isLogging !== oldIsLogging) if (isLogging === false) {
57149
- this.logger.debug(`Stopping logging for CloudTrail Trail ${logicalId}`);
57150
- await this.getClient().send(new StopLoggingCommand({ Name: physicalId }));
57151
- } else {
57152
- this.logger.debug(`Starting logging for CloudTrail Trail ${logicalId}`);
57153
- await this.getClient().send(new StartLoggingCommand({ Name: physicalId }));
57566
+ if (isLogging !== void 0 && isLogging !== oldIsLogging) {
57567
+ if (isLogging === false) {
57568
+ this.logger.debug(`Stopping logging for CloudTrail Trail ${logicalId}`);
57569
+ await this.getClient().send(new StopLoggingCommand({ Name: physicalId }));
57570
+ } else {
57571
+ this.logger.debug(`Starting logging for CloudTrail Trail ${logicalId}`);
57572
+ await this.getClient().send(new StartLoggingCommand({ Name: physicalId }));
57573
+ }
57154
57574
  }
57155
57575
  await this.applyTagDiff(physicalId, previousProperties["Tags"], properties["Tags"]);
57156
57576
  this.logger.debug(`Successfully updated CloudTrail Trail ${logicalId}`);
@@ -57173,7 +57593,8 @@ var CloudTrailProvider = class {
57173
57593
  this.logger.debug(`Successfully deleted CloudTrail Trail ${logicalId}`);
57174
57594
  } catch (error) {
57175
57595
  if (error instanceof TrailNotFoundException) {
57176
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
57596
+ const clientRegion = await this.getClient().config.region();
57597
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
57177
57598
  this.logger.debug(`CloudTrail Trail ${physicalId} does not exist, skipping deletion`);
57178
57599
  return;
57179
57600
  }
@@ -57293,7 +57714,8 @@ var CloudTrailProvider = class {
57293
57714
  result["InsightSelectors"] = insightSelectors;
57294
57715
  let tags = [];
57295
57716
  if (trail.TrailARN) try {
57296
- tags = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsCommand$2({ ResourceIdList: [trail.TrailARN] }))).ResourceTagList?.[0]?.TagsList);
57717
+ const tagsResp = await this.getClient().send(new ListTagsCommand$2({ ResourceIdList: [trail.TrailARN] }));
57718
+ tags = normalizeAwsTagsToCfn(tagsResp.ResourceTagList?.[0]?.TagsList);
57297
57719
  } catch (err) {
57298
57720
  this.logger.debug(`CloudTrail ListTags(${trail.TrailARN}) failed: ${err instanceof Error ? err.message : String(err)}`);
57299
57721
  }
@@ -57583,7 +58005,8 @@ var CodeBuildProvider = class {
57583
58005
  this.logger.debug(`Successfully deleted CodeBuild Project ${logicalId}`);
57584
58006
  } catch (error) {
57585
58007
  if (error instanceof ResourceNotFoundException$13) {
57586
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
58008
+ const clientRegion = await this.getClient().config.region();
58009
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
57587
58010
  this.logger.debug(`CodeBuild Project ${physicalId} does not exist, skipping deletion`);
57588
58011
  return;
57589
58012
  }
@@ -58109,7 +58532,8 @@ var CodeCommitRepositoryProvider = class {
58109
58532
  deletedRepositoryId = (await this.getClient().send(new DeleteRepositoryCommand$1({ repositoryName: physicalId }))).repositoryId;
58110
58533
  } catch (error) {
58111
58534
  if (error instanceof RepositoryDoesNotExistException) {
58112
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
58535
+ const clientRegion = await this.getClient().config.region();
58536
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
58113
58537
  this.logger.debug(`CodeCommit Repository ${physicalId} does not exist, skipping deletion`);
58114
58538
  return;
58115
58539
  }
@@ -58117,7 +58541,8 @@ var CodeCommitRepositoryProvider = class {
58117
58541
  throw new ProvisioningError(`Failed to delete CodeCommit Repository ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
58118
58542
  }
58119
58543
  if (!deletedRepositoryId) {
58120
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
58544
+ const clientRegion = await this.getClient().config.region();
58545
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
58121
58546
  this.logger.debug(`CodeCommit Repository ${physicalId} does not exist, skipping deletion`);
58122
58547
  return;
58123
58548
  }
@@ -58191,7 +58616,8 @@ var CodeCommitRepositoryProvider = class {
58191
58616
  if (!metadata) return void 0;
58192
58617
  let tags = [];
58193
58618
  if (metadata.Arn) try {
58194
- tags = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$16({ resourceArn: metadata.Arn }))).tags);
58619
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$16({ resourceArn: metadata.Arn }));
58620
+ tags = normalizeAwsTagsToCfn(tagsResp.tags);
58195
58621
  } catch (err) {
58196
58622
  if (err instanceof RepositoryDoesNotExistException) return void 0;
58197
58623
  throw err;
@@ -58543,7 +58969,8 @@ var DLMLifecyclePolicyProvider = class {
58543
58969
  this.logger.debug(`Successfully deleted DLM Lifecycle Policy ${logicalId}`);
58544
58970
  } catch (error) {
58545
58971
  if (error instanceof ResourceNotFoundException$14) {
58546
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
58972
+ const clientRegion = await this.getClient().config.region();
58973
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
58547
58974
  this.logger.debug(`DLM Lifecycle Policy ${physicalId} does not exist, skipping deletion`);
58548
58975
  return;
58549
58976
  }
@@ -58782,7 +59209,8 @@ var S3VectorsProvider = class {
58782
59209
  this.logger.debug(`Successfully deleted S3 VectorBucket ${logicalId}`);
58783
59210
  } catch (error) {
58784
59211
  if (this.isNotFoundError(error)) {
58785
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
59212
+ const clientRegion = await this.getClient().config.region();
59213
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
58786
59214
  this.logger.debug(`S3 VectorBucket ${physicalId} does not exist, skipping deletion`);
58787
59215
  return;
58788
59216
  }
@@ -58844,7 +59272,8 @@ var S3VectorsProvider = class {
58844
59272
  if (Object.keys(enc).length > 0) result["EncryptionConfiguration"] = enc;
58845
59273
  }
58846
59274
  if (bucket?.vectorBucketArn) try {
58847
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$17({ resourceArn: bucket.vectorBucketArn }))).tags);
59275
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$17({ resourceArn: bucket.vectorBucketArn }));
59276
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.tags);
58848
59277
  } catch (err) {
58849
59278
  this.logger.debug(`S3Vectors ListTagsForResource(${bucket.vectorBucketArn}) failed: ${err instanceof Error ? err.message : String(err)}`);
58850
59279
  result["Tags"] = [];
@@ -59101,7 +59530,8 @@ var S3DirectoryBucketProvider = class {
59101
59530
  await this.deleteBucketWithEmptyRetry(logicalId, physicalId, allowAutoEmpty);
59102
59531
  } catch (error) {
59103
59532
  if (error instanceof Error && (error.name === "NoSuchBucket" || error.name === "BucketNotFound")) {
59104
- assertRegionMatch(await this.s3Client.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
59533
+ const clientRegion = await this.s3Client.config.region();
59534
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
59105
59535
  this.logger.debug(`Bucket ${physicalId} does not exist, skipping deletion`);
59106
59536
  return;
59107
59537
  }
@@ -59205,10 +59635,11 @@ var S3DirectoryBucketProvider = class {
59205
59635
  async readTags(bucketName) {
59206
59636
  const accountId = await this.getAccountId();
59207
59637
  const resourceArn = await this.buildBucketArn(bucketName, accountId);
59208
- return normalizeAwsTagsToCfn((await this.getS3ControlClient().send(new ListTagsForResourceCommand$18({
59638
+ const resp = await this.getS3ControlClient().send(new ListTagsForResourceCommand$18({
59209
59639
  AccountId: accountId,
59210
59640
  ResourceArn: resourceArn
59211
- }))).Tags);
59641
+ }));
59642
+ return normalizeAwsTagsToCfn(resp.Tags);
59212
59643
  }
59213
59644
  /**
59214
59645
  * Adopt an existing S3 Express Directory Bucket into cdkd state.
@@ -59407,7 +59838,8 @@ var S3TablesProvider = class {
59407
59838
  this.logger.debug(`Successfully deleted S3 Table Bucket ${logicalId}`);
59408
59839
  } catch (error) {
59409
59840
  if (error instanceof NotFoundException$6) {
59410
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
59841
+ const clientRegion = await this.getClient().config.region();
59842
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
59411
59843
  this.logger.debug(`S3 Table Bucket ${physicalId} does not exist, skipping deletion`);
59412
59844
  return;
59413
59845
  }
@@ -59511,7 +59943,8 @@ var S3TablesProvider = class {
59511
59943
  this.logger.debug(`Successfully deleted S3 Tables Namespace ${logicalId}`);
59512
59944
  } catch (error) {
59513
59945
  if (error instanceof NotFoundException$6) {
59514
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
59946
+ const clientRegion = await this.getClient().config.region();
59947
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
59515
59948
  this.logger.debug(`S3 Tables Namespace ${physicalId} does not exist, skipping deletion`);
59516
59949
  return;
59517
59950
  }
@@ -59915,7 +60348,8 @@ var S3TablesProvider = class {
59915
60348
  throw new ProvisioningError(`Failed to resolve S3 Tables Table ${logicalId} (${physicalId}): ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
59916
60349
  }
59917
60350
  if (resolved === "not-found") {
59918
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
60351
+ const clientRegion = await this.getClient().config.region();
60352
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
59919
60353
  this.logger.debug(`S3 Tables Table ${physicalId} does not exist, skipping deletion`);
59920
60354
  return;
59921
60355
  }
@@ -59930,7 +60364,8 @@ var S3TablesProvider = class {
59930
60364
  this.logger.debug(`Successfully deleted S3 Tables Table ${logicalId}`);
59931
60365
  } catch (error) {
59932
60366
  if (error instanceof NotFoundException$6) {
59933
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
60367
+ const clientRegion = await this.getClient().config.region();
60368
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
59934
60369
  this.logger.debug(`S3 Tables Table ${physicalId} does not exist, skipping deletion`);
59935
60370
  return;
59936
60371
  }
@@ -60375,7 +60810,8 @@ var ECRProvider = class {
60375
60810
  } catch (error) {
60376
60811
  if (error instanceof RepositoryNotEmptyException) throw new ProvisioningError(`Failed to delete ECR Repository ${logicalId}: repository ${physicalId} still contains images. Matching CloudFormation, cdkd does not force-delete an image-carrying repository unless it opted in via EmptyOnDelete: true (CDK's emptyOnDelete) or the '${ECR_AUTO_DELETE_IMAGES_TAG}' tag (CDK's autoDeleteImages). Either delete the images first (aws ecr batch-delete-image), or redeploy with emptyOnDelete: true and destroy again.`, resourceType, logicalId, physicalId, error);
60377
60812
  if (error instanceof RepositoryNotFoundException) {
60378
- assertRegionMatch(await this.getClient().config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
60813
+ const clientRegion = await this.getClient().config.region();
60814
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
60379
60815
  this.logger.debug(`ECR Repository ${physicalId} does not exist, skipping deletion`);
60380
60816
  return;
60381
60817
  }
@@ -60442,7 +60878,8 @@ var ECRProvider = class {
60442
60878
  if (!(err instanceof LifecyclePolicyNotFoundException)) throw err;
60443
60879
  }
60444
60880
  if (r.repositoryArn) try {
60445
- result["Tags"] = normalizeAwsTagsToCfn((await this.getClient().send(new ListTagsForResourceCommand$9({ resourceArn: r.repositoryArn }))).tags);
60881
+ const tagsResp = await this.getClient().send(new ListTagsForResourceCommand$9({ resourceArn: r.repositoryArn }));
60882
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.tags);
60446
60883
  } catch (err) {
60447
60884
  if (!(err instanceof RepositoryNotFoundException)) throw err;
60448
60885
  }
@@ -60919,10 +61356,12 @@ async function runDestroyForStack(stackName, state, ctx) {
60919
61356
  });
60920
61357
  try {
60921
61358
  let finalSnapshotIdentifier;
60922
- if (resource.deletionPolicy === "Snapshot" && ctx.skipFinalSnapshot !== true) if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resource.resourceType) && resource.provisionedBy !== "cc-api") finalSnapshotIdentifier = buildFinalSnapshotIdentifier(resource.physicalId, resource.resourceType);
60923
- else if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resource.resourceType)) throw ccRoutedFinalSnapshotError(logicalId, resource.resourceType, "--skip-final-snapshot");
60924
- else if (PRE_DELETE_SNAPSHOT_TYPES.has(resource.resourceType)) await createPreDeleteFinalSnapshot(resource.resourceType, resource.physicalId, logicalId, destroyAwsClients ?? ctx.baseAwsClients, logger);
60925
- else throw unsupportedFinalSnapshotError(logicalId, resource.resourceType, "--skip-final-snapshot");
61359
+ if (resource.deletionPolicy === "Snapshot" && ctx.skipFinalSnapshot !== true) {
61360
+ if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resource.resourceType) && resource.provisionedBy !== "cc-api") finalSnapshotIdentifier = buildFinalSnapshotIdentifier(resource.physicalId, resource.resourceType);
61361
+ else if (ATOMIC_FINAL_SNAPSHOT_TYPES.has(resource.resourceType)) throw ccRoutedFinalSnapshotError(logicalId, resource.resourceType, "--skip-final-snapshot");
61362
+ else if (PRE_DELETE_SNAPSHOT_TYPES.has(resource.resourceType)) await createPreDeleteFinalSnapshot(resource.resourceType, resource.physicalId, logicalId, destroyAwsClients ?? ctx.baseAwsClients, logger);
61363
+ else throw unsupportedFinalSnapshotError(logicalId, resource.resourceType, "--skip-final-snapshot");
61364
+ }
60926
61365
  const provider = destroyProviderRegistry.getProviderFor({
60927
61366
  resourceType: resource.resourceType,
60928
61367
  provisionedBy: resource.provisionedBy
@@ -61377,12 +61816,13 @@ var NestedStackProvider = class {
61377
61816
  }
61378
61817
  const resourceCount = Object.keys(childStateData.state.resources).length;
61379
61818
  this.logger.info(`Destroying nested stack ${childStackName} (logicalId=${logicalId}, ${resourceCount} resource(s))`);
61380
- const childResult = await withNestedStackContext({
61819
+ const childCtx = {
61381
61820
  ...ctx,
61382
61821
  parentStackName: childStackName,
61383
61822
  parentRegion: childRegion,
61384
61823
  nestedTemplates: void 0
61385
- }, () => runDestroyForStack(childStackName, childStateData.state, {
61824
+ };
61825
+ const childResult = await withNestedStackContext(childCtx, () => runDestroyForStack(childStackName, childStateData.state, {
61386
61826
  stateBackend: ctx.stateBackend,
61387
61827
  lockManager: ctx.lockManager,
61388
61828
  providerRegistry: ctx.providerRegistry,
@@ -61429,12 +61869,13 @@ var NestedStackProvider = class {
61429
61869
  parentRegion: parentCtx.parentRegion
61430
61870
  }
61431
61871
  }, childRegion, parentCtx.exportIndexStore);
61432
- await withNestedStackContext({
61872
+ const childCtx = {
61433
61873
  ...parentCtx,
61434
61874
  parentStackName: childStackName,
61435
61875
  parentRegion: childRegion,
61436
61876
  nestedTemplates: grandchildTemplates
61437
- }, () => childEngine.deploy(childStackName, childTemplate));
61877
+ };
61878
+ await withNestedStackContext(childCtx, () => childEngine.deploy(childStackName, childTemplate));
61438
61879
  }
61439
61880
  /**
61440
61881
  * The child's outputs, as `Outputs.<Key>` attributes for the parent's
@@ -61828,7 +62269,8 @@ var ACMCertificateProvider = class {
61828
62269
  throw new ProvisioningError(`${error.message} ${survivorNote}`, resourceType, logicalId, requestedArn, error);
61829
62270
  }
61830
62271
  const cause = error instanceof Error ? error : void 0;
61831
- throw new ProvisioningError(`Failed to create ACM certificate ${logicalId}: ${error instanceof Error ? error.message : String(error)}` + (survivorNote === void 0 ? "" : ` ${survivorNote}`), resourceType, logicalId, requestedArn, cause);
62272
+ const detail = error instanceof Error ? error.message : String(error);
62273
+ throw new ProvisioningError(`Failed to create ACM certificate ${logicalId}: ${detail}` + (survivorNote === void 0 ? "" : ` ${survivorNote}`), resourceType, logicalId, requestedArn, cause);
61832
62274
  }
61833
62275
  }
61834
62276
  /**
@@ -61976,7 +62418,8 @@ var ACMCertificateProvider = class {
61976
62418
  await this.acmClient.send(new DeleteCertificateCommand({ CertificateArn: physicalId }));
61977
62419
  } catch (error) {
61978
62420
  if (error instanceof ResourceNotFoundException$7) {
61979
- assertRegionMatch(await this.acmClient.config.region(), context?.expectedRegion, resourceType, logicalId, physicalId);
62421
+ const clientRegion = await this.acmClient.config.region();
62422
+ assertRegionMatch(clientRegion, context?.expectedRegion, resourceType, logicalId, physicalId);
61980
62423
  this.logger.debug(`Certificate ${physicalId} does not exist, skipping deletion`);
61981
62424
  return;
61982
62425
  }
@@ -62025,7 +62468,8 @@ var ACMCertificateProvider = class {
62025
62468
  if (cert.Options?.CertificateTransparencyLoggingPreference !== void 0) result["CertificateTransparencyLoggingPreference"] = cert.Options.CertificateTransparencyLoggingPreference;
62026
62469
  if (cert.Options?.Export !== void 0) result["CertificateExport"] = cert.Options.Export;
62027
62470
  try {
62028
- result["Tags"] = normalizeAwsTagsToCfn((await this.acmClient.send(new ListTagsForCertificateCommand({ CertificateArn: physicalId }))).Tags);
62471
+ const tagsResp = await this.acmClient.send(new ListTagsForCertificateCommand({ CertificateArn: physicalId }));
62472
+ result["Tags"] = normalizeAwsTagsToCfn(tagsResp.Tags);
62029
62473
  } catch (err) {
62030
62474
  if (!(err instanceof ResourceNotFoundException$7)) throw err;
62031
62475
  }
@@ -62735,23 +63179,24 @@ async function deployCommand(stacks, options) {
62735
63179
  let recreateTargets;
62736
63180
  if (options.recreateViaCcApi?.length || options.recreateViaSdkProvider?.length) {
62737
63181
  const stateForRecreateCheck = await stackStateBackend.getState(stackInfo.stackName, stackRegion);
63182
+ const syncValidation = validateRecreateTargets({
63183
+ template: stackInfo.template,
63184
+ state: stateForRecreateCheck?.state ?? {
63185
+ version: 9,
63186
+ stackName: stackInfo.stackName,
63187
+ region: stackRegion,
63188
+ resources: {},
63189
+ outputs: {},
63190
+ lastModified: Date.now()
63191
+ },
63192
+ recreateViaCcApi: options.recreateViaCcApi ?? [],
63193
+ recreateViaSdkProvider: options.recreateViaSdkProvider ?? [],
63194
+ allowUnsupportedProperties: new Set(options.allowUnsupportedProperties ?? []),
63195
+ forceStatefulRecreation: options.forceStatefulRecreation ?? false,
63196
+ hasSdkProvider: (rt) => stackProviderRegistry.getProviderType(rt) === "sdk"
63197
+ });
62738
63198
  const validation = await probeAndRevalidateStateful({
62739
- validation: validateRecreateTargets({
62740
- template: stackInfo.template,
62741
- state: stateForRecreateCheck?.state ?? {
62742
- version: 9,
62743
- stackName: stackInfo.stackName,
62744
- region: stackRegion,
62745
- resources: {},
62746
- outputs: {},
62747
- lastModified: Date.now()
62748
- },
62749
- recreateViaCcApi: options.recreateViaCcApi ?? [],
62750
- recreateViaSdkProvider: options.recreateViaSdkProvider ?? [],
62751
- allowUnsupportedProperties: new Set(options.allowUnsupportedProperties ?? []),
62752
- forceStatefulRecreation: options.forceStatefulRecreation ?? false,
62753
- hasSdkProvider: (rt) => stackProviderRegistry.getProviderType(rt) === "sdk"
62754
- }),
63199
+ validation: syncValidation,
62755
63200
  clients: {
62756
63201
  s3: stackAwsClients.s3,
62757
63202
  cloudWatchLogs: stackAwsClients.cloudWatchLogs,
@@ -63100,12 +63545,30 @@ function valuesEqual(a, b) {
63100
63545
  * the expression that state stores post-redaction.
63101
63546
  *
63102
63547
  * `storedOutputs` is the bag currently in state. It is consulted for exactly
63103
- * one decision — the LITERAL `Export.Name` of a secret-bearing stack, see issue
63104
- * #1942 at that branch and never merged into the result: everything else here
63548
+ * two decisions — the LITERAL `Export.Name` of a secret-bearing stack, see issue
63549
+ * #1942 at that branch, and whether a skipped-output record still applies, see
63550
+ * issue #2740 below — and never merged into the result: everything else here
63105
63551
  * previews what the template says, and reading state anywhere else would make
63106
63552
  * the preview agree with the past instead of with the next deploy.
63107
- */
63108
- async function resolveTemplateOutputs(template, resolveFn, conditions, storedOutputs) {
63553
+ *
63554
+ * `skippedOutputKeys` (issue #2740) are the keys the LAST DEPLOY could not
63555
+ * resolve and skipped, already narrowed by the caller on BOTH halves of the
63556
+ * binding rule: the recorded digest still matches today's template, AND no
63557
+ * resource the output references is changing on this run
63558
+ * (`bindingSkippedOutputs`, over a pre-resolution snapshot and this run's
63559
+ * resource diff — see `skipped-outputs.ts` for why the snapshot is the
63560
+ * caller's and why the change map cannot come from the digest). Such a key,
63561
+ * while still absent from `storedOutputs`, is previewed as ABSENT — skipped
63562
+ * without resolving, no bag entry, no row — because that is what the next
63563
+ * deploy will leave in state: this resolver runs
63564
+ * with `skipDynamicReferences`, so a failure that happened inside a secret
63565
+ * lookup would otherwise assemble cleanly and preview an `ADD` the deploy will
63566
+ * never perform. NOT a resolution failure: the sibling outputs are previewed
63567
+ * normally, so a genuine change beside the broken output still renders and
63568
+ * `--fail` still exits 1 for it. The key appearing in state (a later deploy
63569
+ * resolved it) ends the suppression.
63570
+ */
63571
+ async function resolveTemplateOutputs(template, resolveFn, conditions, storedOutputs, skippedOutputKeys) {
63109
63572
  const logger = getLogger().child("OutputsDiff");
63110
63573
  const outputs = Object.create(null);
63111
63574
  const exportNames = /* @__PURE__ */ new Set();
@@ -63146,6 +63609,10 @@ async function resolveTemplateOutputs(template, resolveFn, conditions, storedOut
63146
63609
  logger.debug(`Skipping output ${outputKey} — condition ${output.Condition} is false`);
63147
63610
  continue;
63148
63611
  }
63612
+ if (skippedOutputKeys?.has(outputKey) && !(storedOutputs !== void 0 && Object.prototype.hasOwnProperty.call(storedOutputs, outputKey))) {
63613
+ logger.debug(`Diff previewing output ${stripControlChars(outputKey)} as absent — the last deploy could not resolve it and its template inputs are unchanged`);
63614
+ continue;
63615
+ }
63149
63616
  const sourceUsedSub = templateUsesSub(output.Value);
63150
63617
  let value;
63151
63618
  try {
@@ -63174,21 +63641,23 @@ async function resolveTemplateOutputs(template, resolveFn, conditions, storedOut
63174
63641
  resolutionFailed = true;
63175
63642
  continue;
63176
63643
  }
63177
- if (typeof exportName === "string" && !isUnresolvedValue(exportName, exportSourceUsedSub)) if (declaredExportIsIntrinsic && isSecretDynamicReference(exportName)) logger.debug(`Diff skipping export alias of ${stripControlChars(outputKey)} — the name carries a secret reference`);
63178
- else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) logger.debug(`Diff skipping export alias ${stripControlChars(exportName)} of ${stripControlChars(outputKey)} — collides with an output name`);
63179
- else if (!declaredExportIsIntrinsic && secretSourceKeys.size > 0) if (storedOutputs !== void 0 && Object.prototype.hasOwnProperty.call(storedOutputs, exportName)) {
63180
- outputs[exportName] = value;
63181
- exportNames.add(exportName);
63182
- } else {
63183
- logger.debug(`Diff cannot decide the export alias of ${stripControlChars(outputKey)} — a literal name may contain a resolved secret and state does not hold that key`);
63184
- failedKeys.add(exportName);
63185
- resolutionFailed = true;
63186
- }
63187
- else {
63188
- outputs[exportName] = value;
63189
- exportNames.add(exportName);
63190
- }
63191
- else resolutionFailed = true;
63644
+ if (typeof exportName === "string" && !isUnresolvedValue(exportName, exportSourceUsedSub)) {
63645
+ if (declaredExportIsIntrinsic && isSecretDynamicReference(exportName)) logger.debug(`Diff skipping export alias of ${stripControlChars(outputKey)} — the name carries a secret reference`);
63646
+ else if (isExportAliasCollision(exportName, outputKey, publishedOutputNames)) logger.debug(`Diff skipping export alias ${stripControlChars(exportName)} of ${stripControlChars(outputKey)} collides with an output name`);
63647
+ else if (!declaredExportIsIntrinsic && secretSourceKeys.size > 0) {
63648
+ if (storedOutputs !== void 0 && Object.prototype.hasOwnProperty.call(storedOutputs, exportName)) {
63649
+ outputs[exportName] = value;
63650
+ exportNames.add(exportName);
63651
+ } else {
63652
+ logger.debug(`Diff cannot decide the export alias of ${stripControlChars(outputKey)} — a literal name may contain a resolved secret and state does not hold that key`);
63653
+ failedKeys.add(exportName);
63654
+ resolutionFailed = true;
63655
+ }
63656
+ } else {
63657
+ outputs[exportName] = value;
63658
+ exportNames.add(exportName);
63659
+ }
63660
+ } else resolutionFailed = true;
63192
63661
  }
63193
63662
  }
63194
63663
  return {
@@ -64471,6 +64940,10 @@ function tokenValueForComparison(token, declaredType) {
64471
64940
  async function computeStackDiff(currentState, template, region, stackName, stateBackend, diffCalculator, options = {}) {
64472
64941
  const { parameters, canonicalizeProperties, cfnFallback, inheritSecretBearingTemplate } = options;
64473
64942
  const intrinsicResolver = new IntrinsicFunctionResolver(region, { cfnFallback: cfnFallback ?? true });
64943
+ const outputsDigestSource = structuredClone({
64944
+ ...template,
64945
+ Resources: {}
64946
+ });
64474
64947
  let mergedParameters = parameters;
64475
64948
  let parametersBound = false;
64476
64949
  const tokenParameterNames = new Set(Object.entries(parameters ?? {}).filter(([, value]) => carriesDynamicReference(value)).map(([name]) => name));
@@ -64520,7 +64993,9 @@ async function computeStackDiff(currentState, template, region, stackName, state
64520
64993
  skipDynamicReferences: true
64521
64994
  });
64522
64995
  const changes = await diffCalculator.calculateDiff(currentState, effectiveTemplate, resolveFn, canonicalizeProperties);
64523
- const resolved = await resolveTemplateOutputs(effectiveTemplate, resolveFn, conditions, currentState.outputs);
64996
+ const changedLogicalIds = new Set([...changes.values()].filter((c) => c.changeType !== "NO_CHANGE").map((c) => c.logicalId));
64997
+ const bindingSkipped = bindingSkippedOutputs(outputsDigestSource, currentState.skippedOutputs, changedLogicalIds);
64998
+ const resolved = await resolveTemplateOutputs(effectiveTemplate, resolveFn, conditions, currentState.outputs, bindingSkipped);
64524
64999
  let outputChanges = [];
64525
65000
  if (resolved.resolutionFailed) {
64526
65001
  if (computeOutputsDiff(currentState.outputs, resolved.outputs, resolved.exportNames, resolved.secretSourceKeys, {
@@ -64900,7 +65375,6 @@ function renderChangeLines(changes, logFn, ccApiRoutes) {
64900
65375
  case "DELETE":
64901
65376
  deleteCount++;
64902
65377
  logFn(` [-] ${logicalId} (${change.resourceType})`);
64903
- break;
64904
65378
  }
64905
65379
  return {
64906
65380
  create: createCount,
@@ -64975,7 +65449,6 @@ function renderOutputChangeLines(outputChanges, logFn) {
64975
65449
  counts.remove++;
64976
65450
  logFn(` [-] ${name}${exported}`);
64977
65451
  logFn(` old: ${renderOld(change)}`);
64978
- break;
64979
65452
  }
64980
65453
  }
64981
65454
  return counts;
@@ -65166,6 +65639,24 @@ var ScrubNeededError = class ScrubNeededError extends CdkdError {
65166
65639
  * leak" from "scrub refused to look" — the two call for opposite responses
65167
65640
  * (rotate the secret vs. re-spell the reference and re-run).
65168
65641
  */
65642
+ /**
65643
+ * An internal invariant of the exports-index repair, thrown where this code
65644
+ * believes a case is unreachable (issue #2667 review).
65645
+ *
65646
+ * A distinct CLASS because the repair runs inside the per-stack `try` whose
65647
+ * `catch` attributes everything to "the exports index could not be READ": a
65648
+ * plain `Error` there is reported as an S3 read failure, sending an operator to
65649
+ * their bucket policy over a bug in cdkd. That catch rethrows this class
65650
+ * instead, so it surfaces as the per-stack failure it actually is.
65651
+ */
65652
+ var ScrubIndexInvariantError = class ScrubIndexInvariantError extends CdkdError {
65653
+ exitCode = 2;
65654
+ constructor(message) {
65655
+ super(message, "SCRUB_EXPORT_INDEX_INVARIANT");
65656
+ this.name = "ScrubIndexInvariantError";
65657
+ Object.setPrototypeOf(this, ScrubIndexInvariantError.prototype);
65658
+ }
65659
+ };
65169
65660
  var ScrubRefusalError = class ScrubRefusalError extends CdkdError {
65170
65661
  exitCode = 2;
65171
65662
  constructor(message, code) {
@@ -65210,6 +65701,140 @@ function scrubStacksFailedError(failures) {
65210
65701
  return new ScrubRefusalError(`${failures.length} stack(s) could not be scrubbed: ${failures.map((f) => f.stackName).join(", ")}. Each one's reason was logged as it happened — see the 'Scrub of <stack> failed:' line above for it.`, "SCRUB_STACKS_FAILED");
65211
65702
  }
65212
65703
  /**
65704
+ * Which of `entries` this stack's post-scrub `outputs` bag disagrees with.
65705
+ *
65706
+ * OWNERSHIP is read off the entry (`producerStack` / `producerRegion`), never
65707
+ * inferred from the name: a pre-v9 state record makes `importableOutputKeys`
65708
+ * return every output key (`src/types/state.ts`), so a rebuild can key an entry
65709
+ * by a plain output name, and on a name collision the index keeps the latest
65710
+ * writer — so a name alone says nothing about whose entry it is.
65711
+ *
65712
+ * The rule is CONVERGENCE, not a match against scrub's plaintext map. The map
65713
+ * is built by LIVE RESOLUTION against Secrets Manager / SSM, so it holds the
65714
+ * secret's value AS IT IS NOW; an index entry written before a rotation holds
65715
+ * the value as it was THEN, and the two are different strings. Keying on
65716
+ * `state.outputs` instead means the rule reads the record a redeploy and
65717
+ * `ExportIndexStore.rebuild()` both write, and the plaintext map is not
65718
+ * consulted here at all.
65719
+ *
65720
+ * The lookup runs in ONE direction, and the inverse is not available: an
65721
+ * `Export.Name` alias is written into the outputs bag itself
65722
+ * (`deploy-engine.ts`, beside `resolvedExportNames.push(exportName)`), so an
65723
+ * index entry NAME is a key of that bag — while `ExportIndexEntry` carries no
65724
+ * output-key field, so nothing maps an entry back to the output it came from.
65725
+ *
65726
+ * WHAT CAN STILL GO WRONG. A name absent from `outputs` is reported and
65727
+ * nothing is written for it, so an entry in that position keeps its value —
65728
+ * `outputs` is `undefined` here whenever the stack has no state record at all,
65729
+ * which puts every one of its entries in that arm. An entry whose state value
65730
+ * carries no `{{resolve:` is left alone, so a plaintext an index entry holds
65731
+ * whose state counterpart is also plaintext is not reported by this pass and
65732
+ * not written by it. And `entries` is one region's index: an entry in another
65733
+ * region's `exports.json` is outside this call.
65734
+ */
65735
+ function planExportIndexRepair(entries, stackName, producerRegion, outputs) {
65736
+ const examined = [];
65737
+ const findings = [];
65738
+ for (const [exportName, entry] of entries) {
65739
+ if (entry.producerStack !== stackName || entry.producerRegion !== producerRegion) continue;
65740
+ examined.push(exportName);
65741
+ if (outputs === void 0 || !Object.hasOwn(outputs, exportName)) {
65742
+ findings.push({
65743
+ kind: "absent",
65744
+ exportName
65745
+ });
65746
+ continue;
65747
+ }
65748
+ const stateValue = outputs[exportName];
65749
+ if (!carriesDynamicReference(stateValue)) continue;
65750
+ if (JSON.stringify(entry.value) === JSON.stringify(stateValue)) continue;
65751
+ findings.push({
65752
+ kind: "converge",
65753
+ exportName,
65754
+ stateValue
65755
+ });
65756
+ }
65757
+ return {
65758
+ examined,
65759
+ findings
65760
+ };
65761
+ }
65762
+ /**
65763
+ * The error a run ends with when `state.json` was rewritten and the exports
65764
+ * index was not (issue #2667).
65765
+ *
65766
+ * EXPLICIT rather than a warn: an entry this run went to converge and did not
65767
+ * keeps the value it held, in a region-wide object every principal with
65768
+ * `s3:GetObject` on the state bucket can read. `ExportIndexStore.patchEntry`
65769
+ * does not throw on a non-retryable S3 failure or an exhausted If-Match budget
65770
+ * — it logs and returns `false` — so without this the command would exit 0
65771
+ * over exactly that.
65772
+ *
65773
+ * Naming the remainder is what makes the re-run instruction actionable: the
65774
+ * convergence rule reads `state.outputs`, which a completed half already
65775
+ * matches, so a second invocation of the same command decides `converge` only
65776
+ * for the entries still differing.
65777
+ */
65778
+ function exportIndexIncompleteError(unwritten, unreadable, dryRun) {
65779
+ const parts = [dryRun ? "no state written (--dry-run)" : "state.json complete"];
65780
+ if (unwritten.length > 0) parts.push(`${unwritten.length} exports index entr${unwritten.length === 1 ? "y" : "ies"} unwritten (${unwritten.map((u) => `${u.shown} in ${u.region}`).join(", ")})`);
65781
+ if (unreadable.length > 0) parts.push(`${unreadable.length} region(s) whose exports index could not be read (${unreadable.map((u) => `${u.region}: ${u.reason}`).join("; ")})`);
65782
+ return new ScrubRefusalError(`${parts.join("; ")}. Re-run the same command once the cause is cleared: each entry is written only while its value differs from the producer's state.outputs, so a re-run writes the remainder and leaves the rest alone.`, "SCRUB_EXPORT_INDEX_INCOMPLETE");
65783
+ }
65784
+ /**
65785
+ * Converge one scrubbed stack's entries in one region's exports index.
65786
+ *
65787
+ * Run as its OWN step by `scrubCommand`, after `scrubStack` returned — never
65788
+ * from inside the resolver. `scrubStack`'s resolve contexts deliberately
65789
+ * withhold `exportIndex` so `resolveImportValue`'s scan arm cannot `patchEntry`
65790
+ * as a side effect of resolution, at a point in the run nothing chose; that
65791
+ * withholding is unchanged, and this is a separate call with its own condition.
65792
+ *
65793
+ * Under `dryRun` no `patchEntry` is issued. The READ still happens:
65794
+ * `readPersistedEntries` is a GET that does not rebuild, so the audit half runs
65795
+ * without an S3 write.
65796
+ *
65797
+ * TWO PROPERTIES OF THE WRITE a reader will ask about. It runs OUTSIDE the
65798
+ * stack lock — `scrubStack` releases that in its own `finally` — because the
65799
+ * lock guards `state.json`, one key per stack, while `exports.json` is shared
65800
+ * region-wide and carries its own If-Match optimistic lock plus bounded retry;
65801
+ * that is the same concurrency control the deploy path's index write relies on.
65802
+ * And it is ONE PutObject PER ENTRY rather than one for the batch: `patchEntry`
65803
+ * is the operation that changes a value without touching membership, and a
65804
+ * remediation command pays that round trip so a failure names the entry it
65805
+ * could not write.
65806
+ */
65807
+ async function repairExportIndexForStack(store, stackName, producerRegion, outputs, opts) {
65808
+ const entries = await store.readPersistedEntries();
65809
+ if (!entries) return {
65810
+ examined: [],
65811
+ findings: [],
65812
+ unwritten: []
65813
+ };
65814
+ const plan = planExportIndexRepair(entries, stackName, producerRegion, outputs);
65815
+ const unwritten = [];
65816
+ if (opts.dryRun) return {
65817
+ ...plan,
65818
+ unwritten
65819
+ };
65820
+ for (const finding of plan.findings) {
65821
+ if (finding.kind !== "converge") continue;
65822
+ const entry = entries.get(finding.exportName);
65823
+ if (!entry) throw new ScrubIndexInvariantError("exports index repair: a planned entry is absent from the snapshot it was planned from");
65824
+ if (!await store.patchEntry(finding.exportName, {
65825
+ ...entry,
65826
+ value: finding.stateValue
65827
+ }, { requireOwner: {
65828
+ producerStack: stackName,
65829
+ producerRegion
65830
+ } })) unwritten.push(finding.exportName);
65831
+ }
65832
+ return {
65833
+ ...plan,
65834
+ unwritten
65835
+ };
65836
+ }
65837
+ /**
65213
65838
  * `cdkd scrub` — rewrite persisted state so any resolved secret dynamic
65214
65839
  * reference is stored as its UNRESOLVED expression rather than the plaintext
65215
65840
  * value (GHSA fix). "Secret" here is whatever the RESOLVER classifies as one,
@@ -65229,10 +65854,14 @@ function scrubStacksFailedError(failures) {
65229
65854
  * re-resolves each resource's properties to learn the resolved secret VALUES
65230
65855
  * (recorded, never printed or re-persisted), and replaces those values in the
65231
65856
  * state record's `properties` / `attributes` / `observedProperties` with the
65232
- * expression. No AWS resource is created, updated, or deleted; only state.json
65233
- * is rewritten. This is why it is a top-level command and not `cdkd state
65234
- * scrub` — the `cdkd state ...` family operates on the state bucket alone and
65235
- * deliberately needs no CDK code.
65857
+ * expression. No AWS resource is created, updated, or deleted. What it WRITES
65858
+ * is the state bucket: each targeted stack's state.json, and since issue
65859
+ * [#2667](https://github.com/go-to-k/cdkd/issues/2667) — the entries a scrubbed
65860
+ * stack publishes in the shared exports index at
65861
+ * `{prefix}/_index/{region}/exports.json`, as a separate step after that
65862
+ * stack's state.json write. This is why it is a top-level command and not
65863
+ * `cdkd state scrub` — the `cdkd state ...` family operates on the state bucket
65864
+ * alone and deliberately needs no CDK code.
65236
65865
  *
65237
65866
  * IMPORTANT: scrubbing does not un-expose an already-leaked secret. A value
65238
65867
  * that was stored in plaintext should be treated as compromised and ROTATED at
@@ -65290,10 +65919,11 @@ async function scrubCommand(stacks, options) {
65290
65919
  if (!app) throw new Error("CDK app is required (scrub needs the template to identify secret references). Pass --app, set CDKD_APP, or add \"app\" to cdk.json.");
65291
65920
  const region = namedCliRegion(options.region) ?? "us-east-1";
65292
65921
  const stateBucket = await resolveStateBucketWithDefault(options.stateBucket, region);
65293
- setAwsClients(new AwsClients({
65922
+ const awsClients = new AwsClients({
65294
65923
  ...options.region && { region: options.region },
65295
65924
  ...options.profile && { profile: options.profile }
65296
- }));
65925
+ });
65926
+ setAwsClients(awsClients);
65297
65927
  logger.info(synthesisStatusMessage(app, "Synthesizing CDK app..."));
65298
65928
  const synthesizer = new Synthesizer();
65299
65929
  const context = parseContextOptions(options.context);
@@ -65333,6 +65963,20 @@ async function scrubCommand(stacks, options) {
65333
65963
  let totalStacksWithUnscrubbableKeys = 0;
65334
65964
  let totalStacksWithUnverifiableReads = 0;
65335
65965
  const failures = [];
65966
+ const exportIndexStores = /* @__PURE__ */ new Map();
65967
+ const exportIndexFor = (indexRegion) => {
65968
+ const existing = exportIndexStores.get(indexRegion);
65969
+ if (existing) return existing;
65970
+ const store = new ExportIndexStore(stateS3.s3, stateBucket, options.statePrefix, indexRegion, stateBackend);
65971
+ exportIndexStores.set(indexRegion, store);
65972
+ return store;
65973
+ };
65974
+ const examinedIndexNames = /* @__PURE__ */ new Map();
65975
+ let totalIndexEntriesConverged = 0;
65976
+ let totalIndexEntriesAbsent = 0;
65977
+ let totalIndexEntriesUnexamined = 0;
65978
+ const indexUnwritten = [];
65979
+ const indexUnreadable = [];
65336
65980
  for (const stack of targetStacks) {
65337
65981
  const stackRegion = stack.region || region;
65338
65982
  let scrubbed;
@@ -65349,10 +65993,51 @@ async function scrubCommand(stacks, options) {
65349
65993
  if (err instanceof Error && err.stack) logger.debug("Stack trace:", err.stack);
65350
65994
  continue;
65351
65995
  }
65996
+ let indexConverged = 0;
65997
+ try {
65998
+ const repair = await repairExportIndexForStack(exportIndexFor(stackRegion), stack.stackName, stackRegion, scrubbed.outputs, { dryRun: options.dryRun ?? false });
65999
+ const seen = examinedIndexNames.get(stackRegion) ?? /* @__PURE__ */ new Set();
66000
+ for (const name of repair.examined) seen.add(name);
66001
+ examinedIndexNames.set(stackRegion, seen);
66002
+ const named = (exportName) => {
66003
+ const shown = scrubbed.exportNameDisplay(exportName);
66004
+ switch (shown.kind) {
66005
+ case "safe": return `'${shown.text}'`;
66006
+ case "masked": return `(masked: "${shown.text}")`;
66007
+ case "withheld": return "(name withheld: it holds a secret this run recorded)";
66008
+ }
66009
+ };
66010
+ const unwrittenNames = new Set(repair.unwritten);
66011
+ for (const exportName of repair.unwritten) indexUnwritten.push({
66012
+ region: stackRegion,
66013
+ shown: named(exportName)
66014
+ });
66015
+ for (const finding of repair.findings) if (finding.kind === "converge") {
66016
+ indexConverged++;
66017
+ if (!options.dryRun && unwrittenNames.has(finding.exportName)) logger.warn(`Exports index entry ${named(finding.exportName)} (${stackRegion}) differs from ${stack.stackName}'s state.outputs and could NOT be written — it keeps the value it holds.`);
66018
+ else {
66019
+ totalIndexEntriesConverged++;
66020
+ logger.info(`${options.dryRun ? "Would converge" : "Converged"} exports index entry ${named(finding.exportName)} (${stackRegion}) to ${stack.stackName}'s state.outputs value.`);
66021
+ }
66022
+ } else {
66023
+ totalIndexEntriesAbsent++;
66024
+ logger.warn(`Exports index entry ${named(finding.exportName)} (${stackRegion}) is published by ${stack.stackName}, whose state.outputs has no key of that name — nothing was written for it and it keeps the value it holds. Redeploy ${stack.stackName} to rewrite the index from its own outputs.`);
66025
+ }
66026
+ } catch (err) {
66027
+ if (err instanceof ScrubIndexInvariantError) throw err;
66028
+ if (!indexUnreadable.some((u) => u.region === stackRegion)) {
66029
+ const reason = displaySafe(err instanceof Error ? err.message : String(err));
66030
+ indexUnreadable.push({
66031
+ region: stackRegion,
66032
+ reason
66033
+ });
66034
+ logger.error(`Exports index for ${stackRegion} could not be read (first seen while scrubbing ${stack.stackName}): ${reason}`);
66035
+ }
66036
+ }
65352
66037
  if (scrubbed.recordsChanged > 0) {
65353
66038
  totalStacksScrubbed++;
65354
66039
  logger.info(`${options.dryRun ? "Would scrub" : "Scrubbed"} ${scrubbed.recordsChanged} resource record(s) in ${stack.stackName}`);
65355
- } else if (scrubbed.secretBearingKeys === 0 && scrubbed.unverifiableReads === 0) logger.info(`No plaintext secrets found in ${stack.stackName}`);
66040
+ } else if (scrubbed.secretBearingKeys === 0 && scrubbed.unverifiableReads === 0 && indexConverged === 0) logger.info(`No plaintext secrets found in ${stack.stackName}`);
65356
66041
  if (scrubbed.unverifiableReads > 0) {
65357
66042
  totalStacksWithUnverifiableReads++;
65358
66043
  logger.warn(`${scrubbed.unverifiableReads} cross-stack read(s) in ${stack.stackName} could NOT be verified — cdkd declines them by design (see the warnings above), so this stack is not reported clean.`);
@@ -65362,7 +66047,28 @@ async function scrubCommand(stacks, options) {
65362
66047
  logger.warn(`${scrubbed.secretBearingKeys} output KEY(s) in ${stack.stackName} hold plaintext and CANNOT be scrubbed — rename the Export.Name and redeploy (see the warning above).`);
65363
66048
  }
65364
66049
  }
65365
- if (totalStacksScrubbed === 0 && totalStacksWithUnscrubbableKeys === 0 && totalStacksWithUnverifiableReads === 0) {
66050
+ for (const [indexRegion, store] of exportIndexStores) {
66051
+ if (indexUnreadable.some((u) => u.region === indexRegion)) continue;
66052
+ let entries;
66053
+ try {
66054
+ entries = await store.readPersistedEntries();
66055
+ } catch (err) {
66056
+ const reason = displaySafe(err instanceof Error ? err.message : String(err));
66057
+ indexUnreadable.push({
66058
+ region: indexRegion,
66059
+ reason
66060
+ });
66061
+ logger.error(`Exports index for ${indexRegion} could not be read for the coverage report: ${reason}`);
66062
+ continue;
66063
+ }
66064
+ if (!entries) continue;
66065
+ const examined = examinedIndexNames.get(indexRegion) ?? /* @__PURE__ */ new Set();
66066
+ const unexamined = [...entries.keys()].filter((name) => !examined.has(name));
66067
+ if (unexamined.length === 0) continue;
66068
+ totalIndexEntriesUnexamined += unexamined.length;
66069
+ logger.info(`Exports index (${indexRegion}): ${unexamined.length} entr${unexamined.length === 1 ? "y is" : "ies are"} published by a producer this run did not scrub, so nothing here read ${unexamined.length === 1 ? "its" : "their"} value. Coverage composes across the apps sharing this bucket and region — run each app's own cdkd scrub to reach its entries.`);
66070
+ }
66071
+ if (totalStacksScrubbed === 0 && totalStacksWithUnscrubbableKeys === 0 && totalStacksWithUnverifiableReads === 0 && totalIndexEntriesConverged === 0 && indexUnwritten.length === 0 && indexUnreadable.length === 0) {
65366
66072
  if (failures.length === 0) {
65367
66073
  logger.info("\nNo plaintext secrets found in any target stack state. Nothing to scrub.");
65368
66074
  return;
@@ -65373,16 +66079,25 @@ async function scrubCommand(stacks, options) {
65373
66079
  const failureNote = failures.length > 0 ? ` ${failures.length} stack(s) could NOT be scrubbed at all and are NOT covered by this summary — see the errors above.` : "";
65374
66080
  const keyNote = totalStacksWithUnscrubbableKeys > 0 ? ` ${totalStacksWithUnscrubbableKeys} stack(s) hold plaintext in an output KEY, which cdkd scrub cannot rewrite — rename that output's Export.Name and redeploy.` : "";
65375
66081
  const unverifiableNote = totalStacksWithUnverifiableReads > 0 ? ` ${totalStacksWithUnverifiableReads} stack(s) carry a cross-stack read cdkd declines to perform, so their imported values could NOT be checked — export a non-secret value (e.g. the secret's ARN) from the producer, or reference it from within its own account.` : "";
66082
+ const indexNote = (() => {
66083
+ const parts = [];
66084
+ if (totalIndexEntriesConverged > 0) parts.push(options.dryRun ? ` ${totalIndexEntriesConverged} exports index entr${totalIndexEntriesConverged === 1 ? "y" : "ies"} differ from the producer's scrubbed state.outputs and would be converged to it (--dry-run, nothing written).` : ` ${totalIndexEntriesConverged} exports index entr${totalIndexEntriesConverged === 1 ? "y" : "ies"} converged to the producer's state.outputs value. exports.json is written with the same PutObject state.json is, so on a VERSIONED bucket the pre-repair body survives as a noncurrent version and stays readable with GetObject and a VersionId.`);
66085
+ if (totalIndexEntriesAbsent > 0) parts.push(` ${totalIndexEntriesAbsent} exports index entr${totalIndexEntriesAbsent === 1 ? "y is" : "ies are"} published by a scrubbed stack under a name its state.outputs does not carry; nothing was written for ${totalIndexEntriesAbsent === 1 ? "it" : "them"} — redeploy that producer.`);
66086
+ if (totalIndexEntriesUnexamined > 0) parts.push(` ${totalIndexEntriesUnexamined} exports index entr${totalIndexEntriesUnexamined === 1 ? "y" : "ies"} belong to producers outside this run and were NOT examined.`);
66087
+ return parts.join("");
66088
+ })();
65376
66089
  if (options.dryRun) {
65377
- if (totalStacksScrubbed > 0) logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written).${keyNote}${unverifiableNote}${failureNote} ROTATE any exposed secret in Secrets Manager.`);
65378
- else logger.info(`\nPlan: nothing can be scrubbed.${keyNote}${unverifiableNote}${failureNote} ROTATE any exposed secret.`);
66090
+ if (totalStacksScrubbed > 0) logger.info(`\nPlan: ${totalStacksScrubbed} stack(s) hold plaintext secrets and would be scrubbed (--dry-run, no state written).${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret in Secrets Manager.`);
66091
+ else logger.info(`\nPlan: no state record can be rewritten.${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
65379
66092
  if (failures.length > 0) throw scrubStacksFailedError(failures);
66093
+ if (indexUnwritten.length > 0 || indexUnreadable.length > 0) throw exportIndexIncompleteError(indexUnwritten, indexUnreadable, true);
65380
66094
  if (options.fail) throw new ScrubNeededError();
65381
66095
  return;
65382
66096
  }
65383
- if (totalStacksScrubbed > 0) logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The CURRENT state.json no longer holds the plaintext. Where the state bucket is VERSIONED — which cdkd bootstrap enables — the pre-scrub body survives as a noncurrent version, stays readable with GetObject and a VersionId, and scrub does not purge it. So a value that was ever persisted must be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating); rotation is what makes any surviving version harmless.${keyNote}${unverifiableNote}${failureNote}`);
65384
- else logger.info(`\nNothing could be rewritten.${keyNote}${unverifiableNote}${failureNote} ROTATE any exposed secret.`);
66097
+ if (totalStacksScrubbed > 0) logger.info(`\nDone: scrubbed ${totalStacksScrubbed} stack(s). The CURRENT state.json no longer holds the plaintext. Where the state bucket is VERSIONED — which cdkd bootstrap enables — the pre-scrub body survives as a noncurrent version, stays readable with GetObject and a VersionId, and scrub does not purge it. So a value that was ever persisted must be treated as compromised — ROTATE it in Secrets Manager (scrub matches the current value, so scrub BEFORE rotating); rotation is what makes any surviving version harmless.${keyNote}${unverifiableNote}${indexNote}${failureNote}`);
66098
+ else logger.info(`\nNo state record was rewritten.${keyNote}${unverifiableNote}${indexNote}${failureNote} ROTATE any exposed secret.`);
65385
66099
  if (failures.length > 0) throw scrubStacksFailedError(failures);
66100
+ if (indexUnwritten.length > 0 || indexUnreadable.length > 0) throw exportIndexIncompleteError(indexUnwritten, indexUnreadable, false);
65386
66101
  if (options.fail && (totalStacksWithUnscrubbableKeys > 0 || totalStacksWithUnverifiableReads > 0)) throw new ScrubNeededError();
65387
66102
  }
65388
66103
  /**
@@ -67006,7 +67721,8 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
67006
67721
  recordsChanged: 0,
67007
67722
  secretsFound: 0,
67008
67723
  secretBearingKeys: 0,
67009
- unverifiableReads: 0
67724
+ unverifiableReads: 0,
67725
+ exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
67010
67726
  };
67011
67727
  }
67012
67728
  const state = loaded.state;
@@ -67149,7 +67865,9 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
67149
67865
  recordsChanged: 0,
67150
67866
  secretsFound: 0,
67151
67867
  secretBearingKeys: secretBearingKeys.length,
67152
- unverifiableReads: prePassFindings.unverifiable.length
67868
+ unverifiableReads: prePassFindings.unverifiable.length,
67869
+ outputs: state.outputs,
67870
+ exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
67153
67871
  };
67154
67872
  let recordsChanged = 0;
67155
67873
  const newResources = {};
@@ -67168,8 +67886,9 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
67168
67886
  const newOutputs = redactUnaccountedOutputs(outputSecrets.size > 0 ? redactSecretsForState(state.outputs, outputSecrets, outputsSourceUntrusted ? void 0 : outputsTemplateSource, TEMPLATE_SOURCED_RULES) : state.outputs, state.outputs, accountedOutputKeys, allRecordedSecrets(outputSecrets, perResourceSecrets));
67169
67887
  if (JSON.stringify(newOutputs) !== JSON.stringify(state.outputs)) recordsChanged++;
67170
67888
  if (recordsChanged > 0 && !opts.dryRun) {
67889
+ const { skippedOutputs: _droppedByScrub, ...carriedState } = state;
67171
67890
  const nextState = {
67172
- ...state,
67891
+ ...carriedState,
67173
67892
  resources: newResources,
67174
67893
  outputs: newOutputs,
67175
67894
  lastModified: Date.now()
@@ -67180,7 +67899,9 @@ async function scrubStack(stack, region, stateBackend, lockManager, opts) {
67180
67899
  recordsChanged,
67181
67900
  secretsFound: totalSecrets,
67182
67901
  secretBearingKeys: secretBearingKeys.length,
67183
- unverifiableReads: prePassFindings.unverifiable.length
67902
+ unverifiableReads: prePassFindings.unverifiable.length,
67903
+ outputs: newOutputs,
67904
+ exportNameDisplay: (name) => secretSafeKeyDisplay(name, outputSecrets)
67184
67905
  };
67185
67906
  } catch (err) {
67186
67907
  throw maskSecretsInError(err, allRecordedSecrets(outputSecrets, perResourceSecrets));
@@ -69332,18 +70053,19 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
69332
70053
  unorderedPaths
69333
70054
  }), secrets, secretResolutionFailed ? seededSecretPaths : secretPaths, maskSecretPaths);
69334
70055
  const notComparedCause = secretResolutionFailed ? "refused" : unresolvedTokens.size > 0 ? "unresolvedToken" : void 0;
69335
- if (reported.changes.length === 0) if (notComparedCause !== void 0) outcomes.push({
69336
- kind: "notCompared",
69337
- logicalId,
69338
- resourceType: resource.resourceType,
69339
- notComparedCause
69340
- });
69341
- else outcomes.push({
69342
- kind: "clean",
69343
- logicalId,
69344
- resourceType: resource.resourceType
69345
- });
69346
- else outcomes.push({
70056
+ if (reported.changes.length === 0) {
70057
+ if (notComparedCause !== void 0) outcomes.push({
70058
+ kind: "notCompared",
70059
+ logicalId,
70060
+ resourceType: resource.resourceType,
70061
+ notComparedCause
70062
+ });
70063
+ else outcomes.push({
70064
+ kind: "clean",
70065
+ logicalId,
70066
+ resourceType: resource.resourceType
70067
+ });
70068
+ } else outcomes.push({
69347
70069
  kind: "drifted",
69348
70070
  logicalId,
69349
70071
  resourceType: resource.resourceType,
@@ -69524,8 +70246,9 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
69524
70246
  properties: redactedBaseline
69525
70247
  };
69526
70248
  }
70249
+ const { skippedOutputs: _droppedByAccept, ...carriedState } = report.state;
69527
70250
  const newState = {
69528
- ...report.state,
70251
+ ...carriedState,
69529
70252
  resources,
69530
70253
  lastModified: Date.now()
69531
70254
  };
@@ -70345,8 +71068,9 @@ async function runRevert(reports, providerRegistry, stateBackend, stateConfig, a
70345
71068
  };
70346
71069
  }
70347
71070
  if (recordedCount === 0) continue;
71071
+ const { skippedOutputs: _droppedByRevert, ...carriedState } = report.state;
70348
71072
  const newState = {
70349
- ...report.state,
71073
+ ...carriedState,
70350
71074
  resources,
70351
71075
  lastModified: Date.now()
70352
71076
  };
@@ -70585,10 +71309,11 @@ function writeHumanReport(reports) {
70585
71309
  const inspected = inspectedCount;
70586
71310
  const notCompared = notComparedOutcomes(report);
70587
71311
  const checked = inspected - notCompared.length;
70588
- if (drifted.length === 0) if (notCompared.length === 0 && checked === 0 && report.outcomes.length > 0) process.stdout.write(`⚠ ${report.stackName} (${report.region}): no drift detected, but NOTHING was compared — 0 of ${report.outcomes.length} resource${report.outcomes.length === 1 ? "" : "s"} checked (${unsupported.length} unsupported, ${skippedCount} skipped)\n`);
70589
- else if (notCompared.length > 0) process.stdout.write(`⚠ ${report.stackName} (${report.region}): no drift detected, but ${checked} of ${inspected} resource${inspected === 1 ? "" : "s"} fully checked ` + (notCompared.some((n) => n.cause === "readFailed") ? `(${notCompared.length} not fully compared), ` : `(${notCompared.length} only partially compared), `) + `${unsupported.length} unsupported\n`);
70590
- else process.stdout.write(`✓ ${report.stackName} (${report.region}): no drift detected (${checked} resource${checked === 1 ? "" : "s"} checked, ${unsupported.length} unsupported)\n`);
70591
- else {
71312
+ if (drifted.length === 0) {
71313
+ if (notCompared.length === 0 && checked === 0 && report.outcomes.length > 0) process.stdout.write(`⚠ ${report.stackName} (${report.region}): no drift detected, but NOTHING was compared — 0 of ${report.outcomes.length} resource${report.outcomes.length === 1 ? "" : "s"} checked (${unsupported.length} unsupported, ${skippedCount} skipped)\n`);
71314
+ else if (notCompared.length > 0) process.stdout.write(`⚠ ${report.stackName} (${report.region}): no drift detected, but ${checked} of ${inspected} resource${inspected === 1 ? "" : "s"} fully checked ` + (notCompared.some((n) => n.cause === "readFailed") ? `(${notCompared.length} not fully compared), ` : `(${notCompared.length} only partially compared), `) + `${unsupported.length} unsupported\n`);
71315
+ else process.stdout.write(`✓ ${report.stackName} (${report.region}): no drift detected (${checked} resource${checked === 1 ? "" : "s"} checked, ${unsupported.length} unsupported)\n`);
71316
+ } else {
70592
71317
  const word = drifted.length === 1 ? "resource" : "resources";
70593
71318
  process.stdout.write(`\n⚠ ${report.stackName} (${report.region}): drift detected on ${drifted.length} ${word}\n\n`);
70594
71319
  for (const o of drifted) {
@@ -70860,12 +71585,14 @@ async function destroyCommand(stackArgs, options) {
70860
71585
  const refs = stateRefsByName.get(stackName) ?? [];
70861
71586
  const synthStack = appStacks.find((s) => s.stackName === stackName);
70862
71587
  const synthRegion = synthStack?.region;
70863
- if (synthStack?.terminationProtection === true) if (options.removeProtection) logger.warn(`Stack ${stackName} has terminationProtection: true — bypassing because --remove-protection set`);
70864
- else {
70865
- const err = new StackTerminationProtectionError(stackName);
70866
- logger.error(` ✗ ${err.message}`);
70867
- totalErrors++;
70868
- continue;
71588
+ if (synthStack?.terminationProtection === true) {
71589
+ if (options.removeProtection) logger.warn(`Stack ${stackName} has terminationProtection: true — bypassing because --remove-protection set`);
71590
+ else {
71591
+ const err = new StackTerminationProtectionError(stackName);
71592
+ logger.error(` ✗ ${err.message}`);
71593
+ totalErrors++;
71594
+ continue;
71595
+ }
70869
71596
  }
70870
71597
  let stackTargetRegion;
70871
71598
  if (refs.length === 0) {
@@ -75121,7 +75848,7 @@ async function stateListCommand(options) {
75121
75848
  * shape; plain `tree` renders `tree(1)`-style box-drawing.
75122
75849
  */
75123
75850
  async function renderTreeMode(refs, stateBackend, asJson) {
75124
- const roots = buildStackTree(await Promise.all(refs.map(async (ref) => {
75851
+ const entries = await Promise.all(refs.map(async (ref) => {
75125
75852
  if (!ref.region) return { stackName: ref.stackName };
75126
75853
  let state;
75127
75854
  try {
@@ -75139,7 +75866,8 @@ async function renderTreeMode(refs, stateBackend, asJson) {
75139
75866
  ...state?.parentLogicalId !== void 0 && { parentLogicalId: state.parentLogicalId },
75140
75867
  ...state?.parentRegion !== void 0 && { parentRegion: state.parentRegion }
75141
75868
  };
75142
- })));
75869
+ }));
75870
+ const roots = buildStackTree(entries);
75143
75871
  if (asJson) {
75144
75872
  process.stdout.write(`${JSON.stringify(stackTreeToJson(roots), null, 2)}\n`);
75145
75873
  return;
@@ -75905,11 +76633,12 @@ async function listAssetStorageMarkers(s3, bucket) {
75905
76633
  for (const key of keys) {
75906
76634
  const region = key.slice(BOOTSTRAP_MARKER_PREFIX.length, -5);
75907
76635
  try {
75908
- const marker = parseBootstrapMarker(await (await s3.send(new GetObjectCommand({
76636
+ const body = await (await s3.send(new GetObjectCommand({
75909
76637
  Bucket: bucket,
75910
76638
  Key: key,
75911
76639
  ...await expectedOwnerParam(s3)
75912
- }))).Body?.transformToString() ?? "", key);
76640
+ }))).Body?.transformToString() ?? "";
76641
+ const marker = parseBootstrapMarker(body, key);
75913
76642
  entries.push({
75914
76643
  region,
75915
76644
  assetBucket: marker.assetBucket,
@@ -76131,7 +76860,7 @@ async function stateRefreshObservedCommand(stackArgs, options) {
76131
76860
  * Shared rather than constructed per resource because nothing writes to it:
76132
76861
  * `redactSecretsForState` only reads its argument.
76133
76862
  */
76134
- const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
76863
+ const NO_RECORDED_SECRETS$1 = /* @__PURE__ */ new Map();
76135
76864
  /**
76136
76865
  * Warn before `cdkd state orphan` force-releases a lock that is still LIVE.
76137
76866
  *
@@ -76244,7 +76973,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
76244
76973
  unsupported++;
76245
76974
  return;
76246
76975
  }
76247
- resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
76976
+ resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS$1, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
76248
76977
  refreshed++;
76249
76978
  } catch (err) {
76250
76979
  failed++;
@@ -76254,6 +76983,7 @@ async function refreshObservedForStack(stackName, region, stateBackend, lockMana
76254
76983
  await Promise.all(tasks);
76255
76984
  });
76256
76985
  state.lastModified = Date.now();
76986
+ if (refreshed > 0) delete state.skippedOutputs;
76257
76987
  const saveOptions = { expectedEtag: etag };
76258
76988
  if (migrationPending) saveOptions.migrateLegacy = true;
76259
76989
  await stateBackend.saveState(stackName, region, state, saveOptions);
@@ -76454,8 +77184,10 @@ async function rollbackCommand(stackArg, options) {
76454
77184
  let stackAwsClients;
76455
77185
  try {
76456
77186
  let ref;
76457
- if (stackArg) ref = resolveSingleRegion(stackArg, await setup.stateBackend.listStacks(), options.stackRegion);
76458
- else {
77187
+ if (stackArg) {
77188
+ const refs = await setup.stateBackend.listStacks();
77189
+ ref = resolveSingleRegion(stackArg, refs, options.stackRegion);
77190
+ } else {
76459
77191
  const candidates = await findJournalCandidates(setup.stateBackend, setup.prefix);
76460
77192
  const scoped = options.stackRegion ? candidates.filter((c) => c.region === options.stackRegion) : candidates;
76461
77193
  if (scoped.length === 0) {
@@ -76508,11 +77240,13 @@ async function rollbackCommand(stackArg, options) {
76508
77240
  for (let s = journal.segments.length - 1; s >= 0; s--) {
76509
77241
  const segment = journal.segments[s];
76510
77242
  logger.info(`\n Segment ${s + 1}/${journal.segments.length} (${segment.reason}${segment.runId ? `, run ${segment.runId}` : ""}):`);
76511
- if (segment.failedOperations && segment.failedOperations.length > 0) if (options.revertFailed) {
76512
- const failedPlan = planFailedOps(segment.failedOperations, planStateView);
76513
- for (const item of failedPlan) logger.info(failedActionLabel(item, options.skipFinalSnapshot === true));
76514
- applyFailedPlanToPreview(failedPlan, planStateView, options.skipFinalSnapshot === true);
76515
- } else for (const fop of segment.failedOperations) logger.info(` - (left as-is) ${fop.logicalId} (${fop.resourceType}) — its ${fop.changeType} FAILED mid-deploy; pass --revert-failed to attempt reverting it`);
77243
+ if (segment.failedOperations && segment.failedOperations.length > 0) {
77244
+ if (options.revertFailed) {
77245
+ const failedPlan = planFailedOps(segment.failedOperations, planStateView);
77246
+ for (const item of failedPlan) logger.info(failedActionLabel(item, options.skipFinalSnapshot === true));
77247
+ applyFailedPlanToPreview(failedPlan, planStateView, options.skipFinalSnapshot === true);
77248
+ } else for (const fop of segment.failedOperations) logger.info(` - (left as-is) ${fop.logicalId} (${fop.resourceType}) — its ${fop.changeType} FAILED mid-deploy; pass --revert-failed to attempt reverting it`);
77249
+ }
76516
77250
  const plan = planRollback(segment.operations, planStateView, orphanLogicalIds);
76517
77251
  for (const item of plan) logger.info(actionLabel(item, options.skipFinalSnapshot === true));
76518
77252
  applyPlanToPreview(plan, planStateView, options.skipFinalSnapshot === true);
@@ -76541,8 +77275,9 @@ async function rollbackCommand(stackArg, options) {
76541
77275
  };
76542
77276
  let currentEtag = stateData.etag;
76543
77277
  const saveState = async () => {
77278
+ const { skippedOutputs: _droppedByRollback, ...carriedState } = baseState;
76544
77279
  const next = () => ({
76545
- ...baseState,
77280
+ ...carriedState,
76546
77281
  version: 9,
76547
77282
  region,
76548
77283
  resources: { ...stateResources },
@@ -76686,10 +77421,7 @@ function applyPlanToPreview(plan, previewState, skipFinalSnapshot) {
76686
77421
  break;
76687
77422
  case "revert":
76688
77423
  case "reverse-replacement":
76689
- case "reverse-replacement-readopt":
76690
- if (op.previousState) previewState[op.logicalId] = op.previousState;
76691
- break;
76692
- default: break;
77424
+ case "reverse-replacement-readopt": if (op.previousState) previewState[op.logicalId] = op.previousState;
76693
77425
  }
76694
77426
  }
76695
77427
  }
@@ -76709,10 +77441,7 @@ function applyFailedPlanToPreview(plan, previewState, skipFinalSnapshot) {
76709
77441
  case "orphan-failed-create-retain":
76710
77442
  delete previewState[op.logicalId];
76711
77443
  break;
76712
- case "revert-failed-update":
76713
- if (op.previousState) previewState[op.logicalId] = op.previousState;
76714
- break;
76715
- default: break;
77444
+ case "revert-failed-update": if (op.previousState) previewState[op.logicalId] = op.previousState;
76716
77445
  }
76717
77446
  }
76718
77447
  }
@@ -77491,7 +78220,7 @@ function looksLikeEcrHostWithForeignSuffix(imageUri) {
77491
78220
  * a foreign-bucket refusal like the create / teardown sides.
77492
78221
  */
77493
78222
  /** Default `--older-than` when the flag is not passed. */
77494
- const DEFAULT_OLDER_THAN_MS = 720 * 60 * 60 * 1e3;
78223
+ const DEFAULT_OLDER_THAN_MS = 2592e6;
77495
78224
  /** S3 `DeleteObjects` accepts at most 1,000 keys per call. */
77496
78225
  const S3_DELETE_BATCH_SIZE = 1e3;
77497
78226
  /** ECR `BatchDeleteImage` accepts at most 100 image ids per call. */
@@ -77512,7 +78241,7 @@ function parseOlderThan(value) {
77512
78241
  if (!match) throw new Error(`Invalid --older-than "${value}": expected <number>d or <number>h (e.g. 30d, 12h)`);
77513
78242
  const num = Number(match[1]);
77514
78243
  if (!Number.isFinite(num) || num <= 0) throw new Error(`Invalid --older-than "${value}": must be greater than zero`);
77515
- const multiplier = match[2] === "d" ? 1440 * 60 * 1e3 : 3600 * 1e3;
78244
+ const multiplier = match[2] === "d" ? 864e5 : 36e5;
77516
78245
  return Math.round(num * multiplier);
77517
78246
  }
77518
78247
  /**
@@ -77879,7 +78608,8 @@ async function scanReferencedAssets(stateBackend, marker, logger) {
77879
78608
  } catch (error) {
77880
78609
  const described = describeStateKey(key, STATE_FILE_SUFFIX, DEFAULT_STATE_PREFIX);
77881
78610
  const regionMatch = /^(\S+) \((\S+)\)$/.exec(described);
77882
- throw new CdkdError(`State file '${key}' is not valid JSON — aborting: gc must know every referenced asset before deleting anything, and this file's references are unreadable. Repair or remove the corrupt state file ('${regionMatch ? `cdkd state show ${regionMatch[1]} --stack-region ${regionMatch[2]}` : `cdkd state show ${described}`}' to inspect), then re-run.`, "GC_STATE_UNREADABLE", error);
78611
+ const inspectHint = regionMatch ? `cdkd state show ${regionMatch[1]} --stack-region ${regionMatch[2]}` : `cdkd state show ${described}`;
78612
+ throw new CdkdError(`State file '${key}' is not valid JSON — aborting: gc must know every referenced asset before deleting anything, and this file's references are unreadable. Repair or remove the corrupt state file ('${inspectHint}' to inspect), then re-run.`, "GC_STATE_UNREADABLE", error);
77883
78613
  }
77884
78614
  collectAssetReferences(parsed, marker, refs);
77885
78615
  }
@@ -78094,9 +78824,9 @@ function formatBytes(bytes) {
78094
78824
  /** Age of a timestamp relative to now, in whole days (or hours under 1d). */
78095
78825
  function formatAge(date) {
78096
78826
  const ageMs = Date.now() - date.getTime();
78097
- const days = Math.floor(ageMs / (1440 * 60 * 1e3));
78827
+ const days = Math.floor(ageMs / 864e5);
78098
78828
  if (days >= 1) return `${days}d old`;
78099
- return `${Math.max(0, Math.floor(ageMs / (3600 * 1e3)))}h old`;
78829
+ return `${Math.max(0, Math.floor(ageMs / 36e5))}h old`;
78100
78830
  }
78101
78831
  /**
78102
78832
  * Interactive confirmation for the deletion. Follows the repo's
@@ -78591,9 +79321,10 @@ async function rewriteResourceReferences(state, orphanLogicalIds, providerRegist
78591
79321
  }
78592
79322
  const newOutputs = {};
78593
79323
  for (const [name, value] of Object.entries(state.outputs ?? {})) newOutputs[name] = await rewriteValue(value, `outputs.${name}`, `<output:${name}>`, orphanSet, fetcher, rewrites, unresolvable);
79324
+ const { skippedOutputs: _droppedByOrphanRewrite, ...carriedState } = state;
78594
79325
  return {
78595
79326
  state: {
78596
- ...state,
79327
+ ...carriedState,
78597
79328
  resources: newResources,
78598
79329
  outputs: newOutputs,
78599
79330
  lastModified: Date.now()
@@ -78717,20 +79448,21 @@ async function rewriteSubTemplate(template, ownerLogicalId, pathPrefix, orphanSe
78717
79448
  continue;
78718
79449
  }
78719
79450
  const dot = inner.indexOf(".");
78720
- if (dot < 0) if (orphanSet.has(inner)) {
78721
- const replaced = fetcher.ref(inner);
78722
- rewrites.push({
78723
- logicalId: ownerLogicalId,
78724
- path: pathPrefix,
78725
- kind: "sub",
78726
- before: m[0],
78727
- after: replaced,
78728
- orphanLogicalId: inner
78729
- });
78730
- out += replaced;
78731
- didChange = true;
78732
- } else out += m[0];
78733
- else {
79451
+ if (dot < 0) {
79452
+ if (orphanSet.has(inner)) {
79453
+ const replaced = fetcher.ref(inner);
79454
+ rewrites.push({
79455
+ logicalId: ownerLogicalId,
79456
+ path: pathPrefix,
79457
+ kind: "sub",
79458
+ before: m[0],
79459
+ after: replaced,
79460
+ orphanLogicalId: inner
79461
+ });
79462
+ out += replaced;
79463
+ didChange = true;
79464
+ } else out += m[0];
79465
+ } else {
78734
79466
  const target = inner.slice(0, dot);
78735
79467
  const attribute = inner.slice(dot + 1);
78736
79468
  if (orphanSet.has(target)) {
@@ -79118,13 +79850,14 @@ async function publishAssetsCommand(stacks, options) {
79118
79850
  region: baseRegion,
79119
79851
  ...options.profile && { profile: options.profile }
79120
79852
  });
79121
- const resolver = new AssetModeResolver(new S3StateBackend(markerClients.s3, {
79853
+ const backend = new S3StateBackend(markerClients.s3, {
79122
79854
  bucket,
79123
79855
  prefix: options.statePrefix
79124
79856
  }, {
79125
79857
  region: baseRegion,
79126
79858
  ...options.profile && { profile: options.profile }
79127
- }), accountId, { ...options.profile && { profile: options.profile } });
79859
+ });
79860
+ const resolver = new AssetModeResolver(backend, accountId, { ...options.profile && { profile: options.profile } });
79128
79861
  markerAccess = {
79129
79862
  resolver,
79130
79863
  dispose: () => markerClients.destroy()
@@ -79454,13 +80187,15 @@ async function importCommand(stackArg, options) {
79454
80187
  const existingState = existingResult?.state ?? null;
79455
80188
  const existingEtag = existingResult?.etag;
79456
80189
  const migrationPending = existingResult?.migrationPending ?? false;
79457
- if (existingState) if (!selectiveMode) {
79458
- if (!options.force) throw new Error(`State already exists for stack '${stackInfo.stackName}' (${targetRegion}). Auto / whole-stack import rebuilds the entire resource map from the template, which would drop any state entry not re-imported. Pass --force to confirm. To add specific resources without affecting unlisted ones, use --resource <id>=<physicalId> (selective merge — no --force needed).`);
79459
- } else {
79460
- const conflicts = [...overrides.keys()].filter((id) => Object.prototype.hasOwnProperty.call(existingState.resources, id));
79461
- if (conflicts.length > 0 && !options.force) throw new Error(`Selective import would overwrite resource(s) already in state: ${conflicts.join(", ")}. Pass --force to confirm the overwrite, or remove these IDs from --resource / --resource-mapping.`);
79462
- const preservedCount = Object.keys(existingState.resources).filter((id) => !overrides.has(id)).length;
79463
- logger.info(`Merging into existing state for ${stackInfo.stackName} (${targetRegion}): preserving ${preservedCount} unlisted resource(s)` + (conflicts.length > 0 ? `, overwriting ${conflicts.length} listed entry(ies)` : ""));
80190
+ if (existingState) {
80191
+ if (!selectiveMode) {
80192
+ if (!options.force) throw new Error(`State already exists for stack '${stackInfo.stackName}' (${targetRegion}). Auto / whole-stack import rebuilds the entire resource map from the template, which would drop any state entry not re-imported. Pass --force to confirm. To add specific resources without affecting unlisted ones, use --resource <id>=<physicalId> (selective merge — no --force needed).`);
80193
+ } else {
80194
+ const conflicts = [...overrides.keys()].filter((id) => Object.prototype.hasOwnProperty.call(existingState.resources, id));
80195
+ if (conflicts.length > 0 && !options.force) throw new Error(`Selective import would overwrite resource(s) already in state: ${conflicts.join(", ")}. Pass --force to confirm the overwrite, or remove these IDs from --resource / --resource-mapping.`);
80196
+ const preservedCount = Object.keys(existingState.resources).filter((id) => !overrides.has(id)).length;
80197
+ logger.info(`Merging into existing state for ${stackInfo.stackName} (${targetRegion}): preserving ${preservedCount} unlisted resource(s)` + (conflicts.length > 0 ? `, overwriting ${conflicts.length} listed entry(ies)` : ""));
80198
+ }
79464
80199
  }
79465
80200
  for (const overrideId of overrides.keys()) if (!templateLogicalIds.has(overrideId)) throw new Error(`--resource / --resource-mapping references logical ID '${overrideId}' which is not in the synthesized template for stack '${stackInfo.stackName}'. Available IDs: ${[...templateLogicalIds].join(", ")}`);
79466
80201
  const owner = `${process.env["USER"] || "unknown"}@${process.env["HOSTNAME"] || "host"}:${process.pid}`;
@@ -79528,8 +80263,7 @@ async function importCommand(stackArg, options) {
79528
80263
  }
79529
80264
  }
79530
80265
  const stackState = buildStackState(stackInfo.stackName, targetRegion, rows, templateParser, stateTemplate, existingState, selectiveMode);
79531
- await resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger);
79532
- await captureObservedForImportedResources(stackState, providerRegistry, logger);
80266
+ await captureObservedForImportedResources(stackState, providerRegistry, logger, await resolveImportedProperties(stackState, stateTemplate, targetRegion, stateBackend, logger));
79533
80267
  const saveOptions = {};
79534
80268
  if (existingEtag) saveOptions.expectedEtag = existingEtag;
79535
80269
  if (migrationPending) saveOptions.migrateLegacy = true;
@@ -79962,14 +80696,90 @@ function defaultOnlyParameterTemplate(template) {
79962
80696
  * already-stored, which on the v3 baseline is already resolved-shape
79963
80697
  * from a prior import / deploy, so re-resolving is a no-op.
79964
80698
  *
80699
+ * RETURNS the logical ids for which an `observedProperties` baseline must NOT
80700
+ * be captured, because this walk cannot vouch that the persisted `properties`
80701
+ * still SPELL the dynamic reference the template had.
80702
+ * `captureObservedForImportedResources` refuses to write a baseline for those
80703
+ * (issue [#2828](https://github.com/go-to-k/cdkd/issues/2828) review); the
80704
+ * redaction it performs is POSITION-based, so a bag that no longer spells the
80705
+ * reference gives it no evidence and the DECRYPTED readback is persisted in
80706
+ * the clear.
80707
+ *
80708
+ * The predicate is deliberately CONSERVATIVE — it over-refuses — and that is a
80709
+ * decision a review round paid for. Two arms:
80710
+ *
80711
+ * - **Resolution THREW** — refuse, unconditionally. A throw means the
80712
+ * resolver could not finish, so nothing about the persisted bag is
80713
+ * trustworthy evidence. This is BEHAVIOURALLY IDENTICAL to the arm this PR
80714
+ * first shipped: two attempts to sharpen it in between each shipped a leak
80715
+ * (the wrapped-complete-token shape, then the
80716
+ * reference-sourced-from-outside-the-bag shape), and saying so is the
80717
+ * strongest available form of "every sharpening leaked". The arm's own
80718
+ * comment carries both measurements.
80719
+ * - **Resolution SUCCEEDED but LOST an opener.** An `Fn::If` whose condition
80720
+ * was DOWNGRADED resolves without throwing and selects the branch AWS did
80721
+ * not take, so `properties` hold the false-branch literal and no reference
80722
+ * at all. The downgrade needs a parameter with NO `Default` — `cdkd import`
80723
+ * accepts no parameter VALUES from the user, but a `Default`-carrying
80724
+ * parameter IS bound (issue #2321's retry), so only an unbindable one leaves
80725
+ * `evaluateConditions` catching per condition and recording `false`.
80726
+ *
80727
+ * ARM 2 IS KNOWN OVER-BROAD for a PUBLIC `{{resolve:ssm:...}}` reference: a
80728
+ * `String` / `StringList` parameter is stored RESOLVED (issue #1901), so its
80729
+ * opener disappears across the resolve and the resource is refused although
80730
+ * nothing was ever secret, costing it a drift baseline on a common config
80731
+ * pattern. Threading the resolve's own `recordedSecretValues` into the
80732
+ * capture corrects it -- measured on issue
80733
+ * [#2852](https://github.com/go-to-k/cdkd/issues/2852), which carries that
80734
+ * remedy -- because a populated map makes absence from it real evidence of
80735
+ * a public parameter, exactly as the deploy path already relies on.
80736
+ *
80737
+ * The cost of a false refusal is one resource's drift baseline until its next
80738
+ * deploy; the cost of a false pass is a plaintext secret in `state.json`. So an
80739
+ * unmeasurable bag refuses too.
80740
+ *
80741
+ * WHAT CAN STILL GO WRONG. No claim is made here about what this closes, and
80742
+ * that is deliberate: successive review rounds falsified a closure claim, then
80743
+ * a closed-set list, then a "reduces" claim -- the mechanism survived every
80744
+ * round of measurement and the SENTENCES about it did not. What follows states only
80745
+ * the DANGER direction, which can become false only once the residue is gone.
80746
+ *
80747
+ * PLAINTEXT CAN STILL BE PERSISTED by this capture. Known ways, each with its
80748
+ * issue and none of them proven to be all of them:
80749
+ *
80750
+ * - the resolve drops a reference, or trades one away, in a manner the opener
80751
+ * comparison below cannot see --
80752
+ * [#2850](https://github.com/go-to-k/cdkd/issues/2850);
80753
+ * - a parameter binds to a placeholder `Default` while the DEPLOYED value was
80754
+ * the reference, so no opener exists in any template this walk is handed --
80755
+ * [#2854](https://github.com/go-to-k/cdkd/issues/2854);
80756
+ * - `redactSecretsForState` cannot PAIR the readback against the source and
80757
+ * returns the bag unchanged: a reshaped container, an identity key AWS
80758
+ * normalised, a readback key the source lacks, an array whose anchors do not
80759
+ * corroborate -- [#2852](https://github.com/go-to-k/cdkd/issues/2852), which
80760
+ * carries the measured probe table;
80761
+ * - the readback holds a secret with NO counterpart in the source at all, set
80762
+ * out of band -- [#2868](https://github.com/go-to-k/cdkd/issues/2868), filed
80763
+ * as a remit question rather than a defect in this mechanism.
80764
+ *
80765
+ * WHY NO EXHAUSTIVE LIST IS OFFERED, as mechanism rather than framing: this
80766
+ * redaction's only evidence is a POST-HOC comparison between the persisted bag
80767
+ * and the readback, so anything that comparison cannot see is trusted.
80768
+ * Establishing more needs PROVENANCE carried out of the resolver or the
80769
+ * deployer, which is where the structural remedy for all of the above lives.
80770
+ *
80771
+ * The set is returned rather than recorded on the state, because it describes
80772
+ * THIS run's resolution and nothing downstream of the save has any use for it.
80773
+ *
79965
80774
  * Exported for unit testing — internal to the command flow otherwise. The
79966
80775
  * masking this walk applies (issue #2803) is only provable against the REAL
79967
80776
  * resolver, since the plaintext reaches the message through the resolver's
79968
80777
  * own throw rather than through anything this function writes.
79969
80778
  */
79970
80779
  async function resolveImportedProperties(stackState, template, region, stateBackend, logger) {
80780
+ const unsafeObservedBaselineLogicalIds = /* @__PURE__ */ new Set();
79971
80781
  const entries = Object.entries(stackState.resources);
79972
- if (entries.length === 0) return;
80782
+ if (entries.length === 0) return unsafeObservedBaselineLogicalIds;
79973
80783
  const resolver = new IntrinsicFunctionResolver(region);
79974
80784
  let parameters = {};
79975
80785
  let conditions = {};
@@ -80004,8 +80814,9 @@ async function resolveImportedProperties(stackState, template, region, stateBack
80004
80814
  };
80005
80815
  for (const [logicalId, resource] of entries) {
80006
80816
  const recordedSecretValues = /* @__PURE__ */ new Map();
80817
+ const unresolvedProperties = resource.properties ?? {};
80818
+ let threw = false;
80007
80819
  try {
80008
- const unresolvedProperties = resource.properties ?? {};
80009
80820
  const resolved = await resolver.resolve(unresolvedProperties, {
80010
80821
  ...baseContext,
80011
80822
  recordedSecretValues
@@ -80013,7 +80824,46 @@ async function resolveImportedProperties(stackState, template, region, stateBack
80013
80824
  resource.properties = recordedSecretValues.size > 0 ? redactSecretsForState(resolved, recordedSecretValues, unresolvedProperties) : resolved;
80014
80825
  } catch (err) {
80015
80826
  logger.warn(`Failed to resolve intrinsics in Properties for imported resource '${logicalId}' (${resource.resourceType}): ${maskSecretsInText(err instanceof Error ? err.message : String(err), recordedSecretValues)}. State will be written with the raw intrinsic shape, which may cause 'cdkd destroy' to fail on this resource — re-import once every referenced sibling is in state, or remove this resource via 'cdkd state orphan'.` + (unboundParameterNames.length > 0 ? ` This template also declares parameter(s) with no 'Default' that an import cannot bind (${unboundParameterNames.join(", ")}), and 'cdkd import' accepts no parameter values — if this property was built from one of those, re-importing a sibling will not change it: give the parameter a 'Default' in the template and re-import, or correct the recorded properties before the next 'cdkd deploy'.` : ""));
80827
+ threw = true;
80828
+ }
80829
+ if (threw) {
80830
+ unsafeObservedBaselineLogicalIds.add(logicalId);
80831
+ continue;
80016
80832
  }
80833
+ const rawOpeners = countDynamicReferenceOpeners(unresolvedProperties);
80834
+ const persistedOpeners = countDynamicReferenceOpeners(resource.properties);
80835
+ if (rawOpeners === void 0 || persistedOpeners === void 0 || persistedOpeners < rawOpeners) unsafeObservedBaselineLogicalIds.add(logicalId);
80836
+ }
80837
+ return unsafeObservedBaselineLogicalIds;
80838
+ }
80839
+ /** The opener every dynamic reference starts with, secret-bearing or not. */
80840
+ const DYNAMIC_REFERENCE_OPENER = "{{resolve:";
80841
+ /**
80842
+ * How many dynamic-reference OPENERS a property bag carries, counted over its
80843
+ * JSON serialization rather than by walking string leaves — which is the whole
80844
+ * point on the RAW side: a token assembled by `Fn::Join` is SPLIT across array
80845
+ * elements, so `{{resolve:secretsmanager:` survives as a fragment that no scan
80846
+ * for a COMPLETE token would see. `secretValueFromJson` renders exactly that
80847
+ * shape, so the split form is the common one, not the exotic one.
80848
+ *
80849
+ * `undefined` when the bag cannot be serialized. Callers treat that as "cannot
80850
+ * vouch for this bag" rather than as zero — the only safe direction, since the
80851
+ * cost of a false refusal is a drift baseline and the cost of a false pass is a
80852
+ * plaintext secret in `state.json`.
80853
+ */
80854
+ function countDynamicReferenceOpeners(bag) {
80855
+ let text;
80856
+ try {
80857
+ text = JSON.stringify(bag ?? {}) ?? "";
80858
+ } catch {
80859
+ return;
80860
+ }
80861
+ let count = 0;
80862
+ for (let from = 0;;) {
80863
+ const at = text.indexOf(DYNAMIC_REFERENCE_OPENER, from);
80864
+ if (at === -1) return count;
80865
+ count++;
80866
+ from = at + 10;
80017
80867
  }
80018
80868
  }
80019
80869
  function printSummary(rows) {
@@ -80105,6 +80955,14 @@ function collectMultiple(value, previous) {
80105
80955
  return [...previous ?? [], value];
80106
80956
  }
80107
80957
  /**
80958
+ * The empty secrets map the observed-capture redaction below passes. Shared
80959
+ * and module-level because `redactSecretsForState` only ever READS its map,
80960
+ * and because an empty one is not an oversight here but the POSITION-only
80961
+ * configuration the call site's note argues for — the same constant
80962
+ * `cdkd state refresh-observed` passes for the same reason.
80963
+ */
80964
+ const NO_RECORDED_SECRETS = /* @__PURE__ */ new Map();
80965
+ /**
80108
80966
  * Populate `observedProperties` for every resource in a freshly-built
80109
80967
  * import StackState by calling the matching provider's
80110
80968
  * `readCurrentState`. Mirrors what `cdkd deploy` does after each
@@ -80123,11 +80981,25 @@ function collectMultiple(value, previous) {
80123
80981
  * (incremental rollout — see `ResourceProvider.readCurrentState`'s
80124
80982
  * doc-comment) keep `observedProperties: undefined`; the drift comparator
80125
80983
  * falls back to `properties` for those, matching pre-v3 behavior.
80984
+ *
80985
+ * Exported for unit testing -- internal to the command flow otherwise. The
80986
+ * input-space matrix drives THIS function rather than replaying its
80987
+ * `redactSecretsForState` call by hand, so a change to the arguments it passes
80988
+ * cannot leave that fence green.
80989
+ *
80990
+ * Every captured bag is REDACTED before it lands on the record, and a resource
80991
+ * whose redaction POSITION SOURCE is unusable is skipped entirely — see the
80992
+ * call site's own note (issue
80993
+ * [#2828](https://github.com/go-to-k/cdkd/issues/2828)).
80126
80994
  */
80127
- async function captureObservedForImportedResources(stackState, providerRegistry, logger) {
80995
+ async function captureObservedForImportedResources(stackState, providerRegistry, logger, unsafeObservedBaselineLogicalIds) {
80128
80996
  const entries = Object.entries(stackState.resources);
80129
80997
  if (entries.length === 0) return;
80130
80998
  await Promise.all(entries.map(async ([logicalId, resource]) => {
80999
+ if (unsafeObservedBaselineLogicalIds.has(logicalId)) {
81000
+ 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.`);
81001
+ return;
81002
+ }
80131
81003
  try {
80132
81004
  const provider = providerRegistry.getProviderFor({
80133
81005
  resourceType: resource.resourceType,
@@ -80135,9 +81007,9 @@ async function captureObservedForImportedResources(stackState, providerRegistry,
80135
81007
  }).provider;
80136
81008
  if (!provider.readCurrentState) return;
80137
81009
  const observed = await provider.readCurrentState(resource.physicalId, logicalId, resource.resourceType, resource.properties ?? {}, buildReadCurrentStateContext(stackState, logicalId));
80138
- if (observed !== void 0) resource.observedProperties = observed;
81010
+ if (observed !== void 0) resource.observedProperties = redactSecretsForState(observed, NO_RECORDED_SECRETS, resource.properties ?? {}, STATE_SOURCED_READBACK_RULES);
80139
81011
  } catch (err) {
80140
- logger.debug(`observedProperties capture for imported ${logicalId} (${resource.resourceType}) failed: ${err instanceof Error ? err.message : String(err)} — drift will fall back to template properties for this resource until the next successful deploy.`);
81012
+ logger.debug(`observedProperties capture for imported ${logicalId} (${resource.resourceType}) failed: ${err instanceof Error ? err.name : typeof err} — drift will fall back to template properties for this resource until the next successful deploy.`);
80141
81013
  }
80142
81014
  }));
80143
81015
  }
@@ -80307,8 +81179,7 @@ async function importNestedStackChildrenRecursive(args) {
80307
81179
  childStackState.parentStack = parentStackName;
80308
81180
  childStackState.parentLogicalId = childLogicalId;
80309
81181
  childStackState.parentRegion = parentRegion;
80310
- await resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger);
80311
- await captureObservedForImportedResources(childStackState, providerRegistry, logger);
81182
+ await captureObservedForImportedResources(childStackState, providerRegistry, logger, await resolveImportedProperties(childStackState, childStateTemplate, childRegion, stateBackend, logger));
80312
81183
  await stateBackend.saveState(childStackName, childRegion, childStackState);
80313
81184
  logger.info(`✓ Nested stack state written: ${childStackName} (${childRegion}) — ${rows.filter((r) => r.outcome === "imported").length} resource(s) imported.`);
80314
81185
  if (childTreeNode.nested.size > 0) await importNestedStackChildrenRecursive({
@@ -80551,10 +81422,11 @@ async function loadBootstrapContainerRepo(synthRegion, opts) {
80551
81422
  bucket: stateBucket,
80552
81423
  prefix: opts.statePrefix
80553
81424
  };
80554
- const { body, resolvedKey } = await readBootstrapMarkerBody(new S3StateBackend(awsClients.s3, stateConfig, {
81425
+ const stateBackend = new S3StateBackend(awsClients.s3, stateConfig, {
80555
81426
  ...clientRegion !== void 0 && { region: clientRegion },
80556
81427
  ...opts.profile !== void 0 && { profile: opts.profile }
80557
- }), rawRegion, { logPrefix: prefix });
81428
+ });
81429
+ const { body, resolvedKey } = await readBootstrapMarkerBody(stateBackend, rawRegion, { logPrefix: prefix });
80558
81430
  if (body === null) {
80559
81431
  const rawKey = getBootstrapMarkerKey(rawRegion);
80560
81432
  logger.debug(`${prefix}: no bootstrap marker at '${resolvedKey}'${rawKey !== resolvedKey ? ` (nor '${rawKey}')` : ""} in bucket '${stateBucket}' — assuming conventional asset-repo names.`);
@@ -80727,14 +81599,15 @@ var S3LocalStateProvider = class {
80727
81599
  this.opts = opts;
80728
81600
  }
80729
81601
  async load(stackName, synthRegion) {
80730
- const loaded = await loadStateForStack(stackName, synthRegion, {
81602
+ const loadOpts = {
80731
81603
  statePrefix: this.opts.statePrefix,
80732
81604
  ...this.opts.stackRegion !== void 0 && { stackRegion: this.opts.stackRegion },
80733
81605
  ...this.opts.rawStackRegion !== void 0 && { rawStackRegion: this.opts.rawStackRegion },
80734
81606
  ...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
80735
81607
  ...this.opts.region !== void 0 && { region: this.opts.region },
80736
81608
  ...this.opts.profile !== void 0 && { profile: this.opts.profile }
80737
- });
81609
+ };
81610
+ const loaded = await loadStateForStack(stackName, synthRegion, loadOpts);
80738
81611
  if (!loaded) return void 0;
80739
81612
  const outputs = {};
80740
81613
  for (const [k, v] of Object.entries(loaded.state.outputs ?? {})) if (typeof v === "string") outputs[k] = v;
@@ -80747,12 +81620,13 @@ var S3LocalStateProvider = class {
80747
81620
  };
80748
81621
  }
80749
81622
  async buildCrossStackResolver(consumerRegion) {
80750
- const built = await buildCrossStackResolver(consumerRegion, {
81623
+ const buildOpts = {
80751
81624
  statePrefix: this.opts.statePrefix,
80752
81625
  ...this.opts.stateBucket !== void 0 && { stateBucket: this.opts.stateBucket },
80753
81626
  ...this.opts.region !== void 0 && { region: this.opts.region },
80754
81627
  ...this.opts.profile !== void 0 && { profile: this.opts.profile }
80755
- });
81628
+ };
81629
+ const built = await buildCrossStackResolver(consumerRegion, buildOpts);
80756
81630
  if (!built) return void 0;
80757
81631
  this.disposers.push(built.dispose);
80758
81632
  return built.resolver;
@@ -82551,7 +83425,7 @@ async function runDetached(opts) {
82551
83425
  if (collisions.length > 0) logger.warn(`Env var(s) ${collisions.map((k) => JSON.stringify(k)).join(", ")} share a name with a docker-client environment variable or have a malformed name (empty, or containing '=' / NUL), and were NOT passed to the container at all (a colliding name would hijack the docker client; a malformed name cannot form a valid environment variable). Rename the env var if the container needs it.`);
82552
83426
  try {
82553
83427
  const { stdout } = await execFileAsync$3(getDockerCmd(), args, {
82554
- maxBuffer: 10 * 1024 * 1024,
83428
+ maxBuffer: 10485760,
82555
83429
  env: dockerSpawnEnvWithSensitive(passthroughEnv)
82556
83430
  });
82557
83431
  return stdout.trim();
@@ -82785,7 +83659,7 @@ const ASSUMED_ROLE_CACHE = /* @__PURE__ */ new Map();
82785
83659
  */
82786
83660
  const CALLER_IDENTITY_CACHE = /* @__PURE__ */ new Map();
82787
83661
  /** 5-minute safety margin against the recorded STS expiration timestamp. */
82788
- const STS_CREDENTIAL_SAFETY_MARGIN_MS = 300 * 1e3;
83662
+ const STS_CREDENTIAL_SAFETY_MARGIN_MS = 3e5;
82789
83663
  function isCredentialFresh(creds) {
82790
83664
  if (!creds.expiration) return false;
82791
83665
  return creds.expiration.getTime() - Date.now() > STS_CREDENTIAL_SAFETY_MARGIN_MS;
@@ -82893,7 +83767,8 @@ async function assumeRoleForEcr(roleArn, callerRegion, logger) {
82893
83767
  };
82894
83768
  } catch (err) {
82895
83769
  if (err instanceof LocalInvokeBuildError$1) throw err;
82896
- throw new LocalInvokeBuildError$1(`Failed to assume role ${roleArn} for ECR pull: ${err instanceof Error ? err.message : String(err)}. Verify the role exists and its trust policy permits the caller's identity to assume it.`);
83770
+ const reason = err instanceof Error ? err.message : String(err);
83771
+ throw new LocalInvokeBuildError$1(`Failed to assume role ${roleArn} for ECR pull: ${reason}. Verify the role exists and its trust policy permits the caller's identity to assume it.`);
82897
83772
  } finally {
82898
83773
  sts.destroy();
82899
83774
  }
@@ -83513,7 +84388,8 @@ function attachWebSocketServer(opts) {
83513
84388
  * route table.
83514
84389
  */
83515
84390
  async function handleManagementRequest(req, res, registry) {
83516
- if (parseConnectionsPath(req.url ?? "") === null) return false;
84391
+ const url = req.url ?? "";
84392
+ if (parseConnectionsPath(url) === null) return false;
83517
84393
  await handleConnectionsRequest({
83518
84394
  req,
83519
84395
  res,
@@ -83781,8 +84657,9 @@ function createContainerPool(specs, options) {
83781
84657
  readOnly: true
83782
84658
  }] : optMount;
83783
84659
  const containerCodePath = resolveRuntimeCodeMountPath(spec.lambda.runtime);
84660
+ const image = resolveRuntimeImage(spec.lambda.runtime);
83784
84661
  containerId = await runDetached({
83785
- image: resolveRuntimeImage(spec.lambda.runtime),
84662
+ image,
83786
84663
  mounts: [{
83787
84664
  hostPath: spec.codeDir,
83788
84665
  containerPath: containerCodePath,
@@ -85682,7 +86559,7 @@ async function createNetworkAndSidecar(args) {
85682
86559
  logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
85683
86560
  try {
85684
86561
  const { stdout } = await execFileAsync$2(getDockerCmd(), sidecarArgs, {
85685
- maxBuffer: 10 * 1024 * 1024,
86562
+ maxBuffer: 10485760,
85686
86563
  env: dockerSpawnEnvWithSensitive(sidecarSensitiveEnv)
85687
86564
  });
85688
86565
  return stdout.trim();
@@ -86055,7 +86932,7 @@ async function runEcsTask(task, options, state) {
86055
86932
  try {
86056
86933
  const sensitiveEnv = dockerEnvs.get(container.name);
86057
86934
  const { stdout } = await execFileAsync$1(getDockerCmd(), args, {
86058
- maxBuffer: 10 * 1024 * 1024,
86935
+ maxBuffer: 10485760,
86059
86936
  env: dockerSpawnEnvWithSensitive(sensitiveEnv)
86060
86937
  });
86061
86938
  id = stdout.trim();
@@ -86146,9 +87023,7 @@ async function awaitDependencies(container, started) {
86146
87023
  if (code !== 0) throw new EcsTaskRunnerError(`Container '${container.name}' requires dependency '${dep.containerName}' to exit 0, but it exited ${code}.`);
86147
87024
  break;
86148
87025
  }
86149
- case "HEALTHY":
86150
- await waitForContainerHealthy(entry.id, dep.containerName);
86151
- break;
87026
+ case "HEALTHY": await waitForContainerHealthy(entry.id, dep.containerName);
86152
87027
  }
86153
87028
  }
86154
87029
  }
@@ -86161,7 +87036,7 @@ async function awaitDependencies(container, started) {
86161
87036
  */
86162
87037
  async function waitForContainerHealthy(containerId, displayName) {
86163
87038
  const logger = getLogger().child("ecs-runner");
86164
- const deadline = Date.now() + 300 * 1e3;
87039
+ const deadline = Date.now() + 3e5;
86165
87040
  let lastStatus = "";
86166
87041
  const inspectArgs = [
86167
87042
  "inspect",
@@ -86190,7 +87065,7 @@ async function waitForContainerHealthy(containerId, displayName) {
86190
87065
  async function waitForContainerExit(containerId) {
86191
87066
  const waitArgs = ["wait", containerId];
86192
87067
  try {
86193
- const { stdout } = await execFileAsync$1(getDockerCmd(), waitArgs, { maxBuffer: 1024 * 1024 });
87068
+ const { stdout } = await execFileAsync$1(getDockerCmd(), waitArgs, { maxBuffer: 1048576 });
86194
87069
  const code = Number.parseInt(stdout.trim(), 10);
86195
87070
  return Number.isFinite(code) ? code : 1;
86196
87071
  } catch (err) {
@@ -86389,11 +87264,12 @@ function buildDockerRunArgs(opts) {
86389
87264
  for (const mp of container.mountPoints) {
86390
87265
  const v = volumeByName.get(mp.sourceVolume);
86391
87266
  if (!v) continue;
86392
- if (v.kind === "host") if (v.hostPath) {
86393
- const ro = mp.readOnly ? ":ro" : "";
86394
- args.push("-v", `${v.hostPath}:${mp.containerPath}${ro}`);
86395
- } else args.push("-v", mp.containerPath);
86396
- else {
87267
+ if (v.kind === "host") {
87268
+ if (v.hostPath) {
87269
+ const ro = mp.readOnly ? ":ro" : "";
87270
+ args.push("-v", `${v.hostPath}:${mp.containerPath}${ro}`);
87271
+ } else args.push("-v", mp.containerPath);
87272
+ } else {
86397
87273
  const name = v.dockerVolumeName ?? v.name;
86398
87274
  const ro = mp.readOnly ? ":ro" : "";
86399
87275
  args.push("-v", `${name}:${mp.containerPath}${ro}`);
@@ -86418,7 +87294,8 @@ function buildDockerRunArgs(opts) {
86418
87294
  applyOverrideMap(finalEnv, overrides["Parameters"]);
86419
87295
  applyOverrideMap(finalEnv, overrides[container.name]);
86420
87296
  }
86421
- const { flags, sensitiveEnv, collisions } = partitionSensitiveEnv(finalEnv, /* @__PURE__ */ new Set([...SENSITIVE_ENV_KEYS, ...secrets.map((s) => s.name)]));
87297
+ const sensitiveKeys = /* @__PURE__ */ new Set([...SENSITIVE_ENV_KEYS, ...secrets.map((s) => s.name)]);
87298
+ const { flags, sensitiveEnv, collisions } = partitionSensitiveEnv(finalEnv, sensitiveKeys);
86422
87299
  args.push(...flags);
86423
87300
  if (container.user) args.push("--user", container.user);
86424
87301
  if (container.privileged) args.push("--privileged");
@@ -86533,16 +87410,20 @@ async function localRunTaskCommand(target, options) {
86533
87410
  const { context: imageContext, stateRecordRegion } = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
86534
87411
  const task = resolveEcsTaskTarget(target, stacks, imageContext);
86535
87412
  logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
86536
- const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
87413
+ const taskStack = stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack;
87414
+ const taskNeeds = detectEcsImageResolutionNeeds(taskStack);
86537
87415
  if (stateProvider && taskNeeds.needsCrossStackResolver) {
86538
87416
  const consumerRegion = resolveEcsConsumerRegion(stateRecordRegion, options, task.stack.region);
86539
87417
  const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
86540
- if (resolver) await applyCrossStackResolverToTask(task, {
86541
- resources: imageContext?.stateResources ?? {},
86542
- ...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
86543
- consumerRegion,
86544
- crossStackResolver: resolver
86545
- });
87418
+ if (resolver) {
87419
+ const subContext = {
87420
+ resources: imageContext?.stateResources ?? {},
87421
+ ...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
87422
+ consumerRegion,
87423
+ crossStackResolver: resolver
87424
+ };
87425
+ await applyCrossStackResolverToTask(task, subContext);
87426
+ }
86546
87427
  } else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass --from-state (cdkd-deployed) or --from-cfn-stack (cdk-deployed) to substitute them against deployed state.");
86547
87428
  sigintHandler = () => {
86548
87429
  sigintCount += 1;
@@ -87082,7 +87963,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
87082
87963
  `${containerId}:${workdirDest}`
87083
87964
  ];
87084
87965
  try {
87085
- await execFileAsync(dockerCmd, cpArgs, { maxBuffer: 64 * 1024 * 1024 });
87966
+ await execFileAsync(dockerCmd, cpArgs, { maxBuffer: 67108864 });
87086
87967
  } catch (err) {
87087
87968
  throw new CdkdError(`softReloadAgentContainer: docker cp into '${containerId}:${workdir}' failed: ${describeDockerExecFailure(err, cpArgs)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_CP_FAILED");
87088
87969
  }
@@ -87621,8 +88502,9 @@ async function buildSigV4HeadersIfRequested(options, resolved, loaded, host, por
87621
88502
  }
87622
88503
  const region = canonicalizeRegion(options.region ?? options.stackRegion ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? resolved.stack.region);
87623
88504
  if (!region) throw new CdkdError("--sigv4: no region resolved for the AgentCore signing scope. Pass --region <region>, set AWS_REGION, or use --from-cfn-stack with a region-bound stack.", "LOCAL_INVOKE_AGENTCORE_SIGV4_NO_REGION");
88505
+ const credentials = await resolveHostCredentialsForSigV4(options, resolved, loaded, region);
87624
88506
  const signed = await signAgentCoreInvocation({
87625
- credentials: await resolveHostCredentialsForSigV4(options, resolved, loaded, region),
88507
+ credentials,
87626
88508
  region,
87627
88509
  host,
87628
88510
  port,
@@ -88742,15 +89624,16 @@ async function localInvokeCommand(target, options) {
88742
89624
  }
88743
89625
  let resolvedAssumeRoleArn;
88744
89626
  if (typeof options.assumeRole === "string") resolvedAssumeRoleArn = options.assumeRole;
88745
- else if (options.assumeRole === true) if (!stateForRoleHint) logger.warn("--assume-role passed without an ARN, but no cdkd state was loaded. Pair it with --from-state, or pass the ARN explicitly: --assume-role <arn>. Falling back to the developer's shell credentials.");
88746
- else {
88747
- const arn = resolveExecutionRoleArnFromState(stateForRoleHint, lambda.logicalId);
88748
- if (arn) {
88749
- resolvedAssumeRoleArn = arn;
88750
- logger.info(`--assume-role: auto-resolved execution role from cdkd state: ${arn}`);
88751
- } else logger.warn(`--assume-role: could not resolve the execution role ARN from cdkd state for '${lambda.logicalId}'. Pass the ARN explicitly: --assume-role <arn>. Falling back to the developer's shell credentials.`);
88752
- }
88753
- else maybeSuggestAssumeRole(options, stateForRoleHint, lambda.logicalId);
89627
+ else if (options.assumeRole === true) {
89628
+ if (!stateForRoleHint) logger.warn("--assume-role passed without an ARN, but no cdkd state was loaded. Pair it with --from-state, or pass the ARN explicitly: --assume-role <arn>. Falling back to the developer's shell credentials.");
89629
+ else {
89630
+ const arn = resolveExecutionRoleArnFromState(stateForRoleHint, lambda.logicalId);
89631
+ if (arn) {
89632
+ resolvedAssumeRoleArn = arn;
89633
+ logger.info(`--assume-role: auto-resolved execution role from cdkd state: ${arn}`);
89634
+ } else logger.warn(`--assume-role: could not resolve the execution role ARN from cdkd state for '${lambda.logicalId}'. Pass the ARN explicitly: --assume-role <arn>. Falling back to the developer's shell credentials.`);
89635
+ }
89636
+ } else maybeSuggestAssumeRole(options, stateForRoleHint, lambda.logicalId);
88754
89637
  const event = await readEvent(options);
88755
89638
  const dockerEnv = {
88756
89639
  AWS_LAMBDA_FUNCTION_NAME: lambda.logicalId,
@@ -88807,7 +89690,8 @@ async function localInvokeCommand(target, options) {
88807
89690
  };
88808
89691
  process.on("SIGINT", sigintHandler);
88809
89692
  await waitForRieReady(containerHost, hostPort, 5e3);
88810
- const result = await invokeRie(containerHost, hostPort, event, Math.max(3e4, lambda.timeoutSec * 2 * 1e3));
89693
+ const invokeTimeoutMs = Math.max(3e4, lambda.timeoutSec * 2 * 1e3);
89694
+ const result = await invokeRie(containerHost, hostPort, event, invokeTimeoutMs);
88811
89695
  await new Promise((resolveDelay) => setTimeout(resolveDelay, 250));
88812
89696
  process.stdout.write(`${result.raw}\n`);
88813
89697
  } finally {
@@ -89526,4 +90410,4 @@ function buildProgram() {
89526
90410
 
89527
90411
  //#endregion
89528
90412
  export { buildProgram };
89529
- //# sourceMappingURL=program-BPeZLD0q.js.map
90413
+ //# sourceMappingURL=program-BXH3FeeV.js.map