@go-to-k/cdkd 0.280.46 → 0.281.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { $ as requireConfigArray, $t as expectedOwnerParam, A as green, An as withErrorHandling, At as getDockerCmd, B as slowCcOperationTimeoutMs, Bt as resolveAutoAssetStorage, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackHasActiveImportsError, Ct as ensureAssetStorage, D as bold, Dt as validateContainerRepoName, E as formatResourceLine, Et as validateAssetBucketName, F as clearOnUpdateRemoval, Ft as Synthesizer, G as refStateLookupFromResource, Gt as resolveUseCdkBootstrapAssets, H as isTerminationProtectionPropagationError, Ht as resolveSkipPrefix, I as ProviderRegistry, It as synthesisStatusMessage, J as resolveExplicitPhysicalId, Jt as CFN_TEMPLATE_BODY_LIMIT, K as WAFv2WebACLProvider, Kt as stateBucketExistenceConfirmed, L as findActionableSilentDrops, Lt as getDefaultStateBucketName, M as yellow, Mt as runDockerStreaming, N as IAMRoleProvider, Nt as AssetManifestLoader, O as cyan, Ot as buildDockerImage, P as collectInlinePolicyNamesManagedBySiblings, Pt as getDockerImageBySourceHash, Q as replayWarn, Qt as uploadCfnTemplate, R as findSilentDropProperties, Rt as getLegacyStateBucketName, S as extractDeploymentEventError, Sn as ResourceUpdateNotSupportedError, St as BOOTSTRAP_MARKER_PREFIX, T as renderStatefulReason, Tt as parseBootstrapMarker, U as IntrinsicFunctionResolver, Ut as resolveStateBucketWithDefault, V as disableInstanceApiTermination, Vt as resolveCaptureObservedState, W as cfnRefValueFromPhysicalId, Wt as resolveStateBucketWithDefaultAndSource, X as configStringRefusal, Xt as MIGRATE_TMP_PREFIX, Y as assertRegionMatch, Yt as CFN_TEMPLATE_URL_LIMIT, Z as readConfigString, Zt as findLargeInlineResources, _ as createPreDeleteFinalSnapshot, _n as MissingCdkCliError, _t as buildAssetRedirectMap, a as DeploymentEventsStore, an as getAwsClients, at as withRetry, b as unsupportedFinalSnapshotError, bn as ProvisioningError, bt as rewriteTemplateAssetReferences, c as replayFailedOperations, ct as DagBuilder, d as IMPLICIT_DELETE_DEPENDENCIES, dt as S3StateBackend, en as AssemblyReader, et as requireConfigObject, f as computeImplicitDeleteEdges, fn as DeployCancelledError, ft as rebuildClientForBucketRegion, g as ccRoutedFinalSnapshotError, gt as WorkGraph, h as buildFinalSnapshotIdentifier, hn as LocalStartServiceError, ht as stringifyValue, i as DeploymentEventsReader, in as AwsClients, it as describeTypeWithThrottleRetry, j as red, jt as runDockerForeground, k as gray, kn as normalizeAwsError, kt as formatDockerLoginError, l as replayRollback, ln as CdkdError, lt as TemplateParser, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalMigrateError, mt as AssetPublisher, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as applyRoleArnIfSet, o as planFailedOps, on as resetAwsClients, ot as isRetryableTransientError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalInvokeBuildError$1, pt as shouldRetainResource, q as normalizeAwsTagsToCfn, qt as warnDeprecatedNoPrefixCliFlag, r as DeployEngine, rn as resolveBucketRegion, rt as DiffCalculator, s as planRollback, sn as setAwsClients, st as isThrottlingError, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as processStackMessages, tt as requireConfigString, u as withResourceDeadline, ut as LockManager, v as isFinalSnapshotError, vn as NestedStackChildDirectDestroyError, vt as createAssetRedirectResolver, w as isStatefulRecreateTargetSync, wn as StackTerminationProtectionError, wt as getBootstrapMarkerKey, x as makeCanonicalizePropertiesFn, xn as ResourceTimeoutError, xt as AssetModeResolver, y as refusesFinalSnapshot, yn as PartialFailureError, yt as loadPublishableAssetManifest, z as CloudControlProvider, zt as resolveApp } from "./deploy-engine-E_JV4UqE.js";
2
+ import { $ as requireConfigArray, $t as expectedOwnerParam, A as green, An as withErrorHandling, At as getDockerCmd, B as slowCcOperationTimeoutMs, Bt as resolveAutoAssetStorage, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as StackHasActiveImportsError, Ct as ensureAssetStorage, D as bold, Dt as validateContainerRepoName, E as formatResourceLine, Et as validateAssetBucketName, F as clearOnUpdateRemoval, Ft as Synthesizer, G as refStateLookupFromResource, Gt as resolveUseCdkBootstrapAssets, H as isTerminationProtectionPropagationError, Ht as resolveSkipPrefix, I as ProviderRegistry, It as synthesisStatusMessage, J as resolveExplicitPhysicalId, Jt as CFN_TEMPLATE_BODY_LIMIT, K as WAFv2WebACLProvider, Kt as stateBucketExistenceConfirmed, L as findActionableSilentDrops, Lt as getDefaultStateBucketName, M as yellow, Mt as runDockerStreaming, N as IAMRoleProvider, Nt as AssetManifestLoader, O as cyan, Ot as buildDockerImage, P as collectInlinePolicyNamesManagedBySiblings, Pt as getDockerImageBySourceHash, Q as replayWarn, Qt as uploadCfnTemplate, R as findSilentDropProperties, Rt as getLegacyStateBucketName, S as extractDeploymentEventError, Sn as ResourceUpdateNotSupportedError, St as BOOTSTRAP_MARKER_PREFIX, T as renderStatefulReason, Tt as parseBootstrapMarker, U as IntrinsicFunctionResolver, Ut as resolveStateBucketWithDefault, V as disableInstanceApiTermination, Vt as resolveCaptureObservedState, W as cfnRefValueFromPhysicalId, Wt as resolveStateBucketWithDefaultAndSource, X as configStringRefusal, Xt as MIGRATE_TMP_PREFIX, Y as assertRegionMatch, Yt as CFN_TEMPLATE_URL_LIMIT, Z as readConfigString, Zt as findLargeInlineResources, _ as createPreDeleteFinalSnapshot, _n as MissingCdkCliError, _t as buildAssetRedirectMap, a as DeploymentEventsStore, an as getAwsClients, at as withRetry, b as unsupportedFinalSnapshotError, bn as ProvisioningError, bt as rewriteTemplateAssetReferences, c as replayFailedOperations, ct as DagBuilder, d as IMPLICIT_DELETE_DEPENDENCIES, dt as S3StateBackend, en as AssemblyReader, et as requireConfigObject, f as computeImplicitDeleteEdges, fn as DeployCancelledError, ft as rebuildClientForBucketRegion, g as ccRoutedFinalSnapshotError, gt as WorkGraph, h as buildFinalSnapshotIdentifier, hn as LocalStartServiceError, ht as stringifyValue, i as DeploymentEventsReader, in as AwsClients, it as describeTypeWithThrottleRetry, j as red, jt as runDockerForeground, k as gray, kn as normalizeAwsError, kt as formatDockerLoginError, l as replayRollback, ln as CdkdError, lt as TemplateParser, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalMigrateError, mt as AssetPublisher, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nt as applyRoleArnIfSet, o as planFailedOps, on as resetAwsClients, ot as isRetryableTransientError, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as LocalInvokeBuildError$1, pt as shouldRetainResource, q as normalizeAwsTagsToCfn, qt as warnDeprecatedNoPrefixCliFlag, r as DeployEngine, rn as resolveBucketRegion, rt as DiffCalculator, s as planRollback, sn as setAwsClients, st as isThrottlingError, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as processStackMessages, tt as requireConfigString, u as withResourceDeadline, ut as LockManager, v as isFinalSnapshotError, vn as NestedStackChildDirectDestroyError, vt as createAssetRedirectResolver, w as isStatefulRecreateTargetSync, wn as StackTerminationProtectionError, wt as getBootstrapMarkerKey, x as makeCanonicalizePropertiesFn, xn as ResourceTimeoutError, xt as AssetModeResolver, y as refusesFinalSnapshot, yn as PartialFailureError, yt as loadPublishableAssetManifest, z as CloudControlProvider, zt as resolveApp } from "./deploy-engine-Bebl98bx.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, h as withStackName, i as runStackBuffered, m as withSkipPrefix, n as getLogger, o as PATTERN_B_NAME_OPTIONS, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-zRrlbaQt.js";
4
- import { t as ASGProvider } from "./asg-provider-DGHuyUA8.js";
4
+ import { t as ASGProvider } from "./asg-provider-BSx4Vrth.js";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { CopyObjectCommand, CreateBucketCommand, DeleteBucketAnalyticsConfigurationCommand, DeleteBucketCommand, DeleteBucketCorsCommand, DeleteBucketEncryptionCommand, DeleteBucketIntelligentTieringConfigurationCommand, DeleteBucketInventoryConfigurationCommand, DeleteBucketLifecycleCommand, DeleteBucketMetricsConfigurationCommand, DeleteBucketOwnershipControlsCommand, DeleteBucketPolicyCommand, DeleteBucketReplicationCommand, DeleteBucketTaggingCommand, DeleteBucketWebsiteCommand, DeleteObjectsCommand, GetBucketAccelerateConfigurationCommand, GetBucketCorsCommand, GetBucketEncryptionCommand, GetBucketLifecycleConfigurationCommand, GetBucketLocationCommand, GetBucketLoggingCommand, GetBucketNotificationConfigurationCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketReplicationCommand, GetBucketTaggingCommand, GetBucketVersioningCommand, GetBucketWebsiteCommand, GetObjectCommand, GetObjectLockConfigurationCommand, GetPublicAccessBlockCommand, HeadBucketCommand, ListBucketAnalyticsConfigurationsCommand, ListBucketIntelligentTieringConfigurationsCommand, ListBucketInventoryConfigurationsCommand, ListBucketMetricsConfigurationsCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchBucket, PutBucketAccelerateConfigurationCommand, PutBucketAnalyticsConfigurationCommand, PutBucketCorsCommand, PutBucketEncryptionCommand, PutBucketIntelligentTieringConfigurationCommand, PutBucketInventoryConfigurationCommand, PutBucketLifecycleConfigurationCommand, PutBucketLoggingCommand, PutBucketMetricsConfigurationCommand, PutBucketNotificationConfigurationCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketReplicationCommand, PutBucketTaggingCommand, PutBucketVersioningCommand, PutBucketWebsiteCommand, PutObjectCommand, PutObjectLockConfigurationCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -591,6 +591,22 @@ const useCdkBootstrapAssetsOption = new Option("--use-cdk-bootstrap-assets", "Pu
591
591
  */
592
592
  const strictGetattOption = new Option("--strict-getatt", "Fail the deploy on ANY Fn::GetAtt that falls back to the physical ID because cdkd cannot construct the attribute (default: warn and use the physical ID, hard-failing only when the fallback is knowably wrong — an *Arn attribute with a non-ARN value or a *Url attribute with a non-http(s) value). Also fails the deploy when a stack Output cannot be resolved (default: warn and store no value).").default(false);
593
593
  /**
594
+ * Issue #1697 — `--no-cfn-fallback` (deploy + diff). By default, a
595
+ * cross-stack reference (`Fn::ImportValue` / `Fn::GetStackOutput`) that is
596
+ * not found in cdkd state falls back to CloudFormation (`ListExports` /
597
+ * `DescribeStacks` outputs) so a cdkd-deployed consumer can reference a
598
+ * producer stack still managed by CloudFormation (`cdk deploy` / raw CFn).
599
+ * The fallback fires only after a cdkd-state miss (cdkd-first precedence),
600
+ * resolves as a WEAK reference (not recorded into `state.imports` /
601
+ * `state.outputReads`; no destroy-time protection on either side), and
602
+ * degrades gracefully when the caller lacks the CloudFormation read
603
+ * permissions (warning + the original not-found error). This flag disables
604
+ * the fallback entirely for users who want cdkd-state-only resolution —
605
+ * e.g. to keep IAM minimal or to keep an export-name typo failing fast
606
+ * instead of accidentally matching an unrelated CloudFormation export.
607
+ */
608
+ const noCfnFallbackOption = new Option("--no-cfn-fallback", "Do not fall back to CloudFormation (ListExports / stack outputs) when an Fn::ImportValue / Fn::GetStackOutput reference is not found in cdkd state (default: fall back, so producers still managed by CloudFormation can be referenced)");
609
+ /**
594
610
  * CDK annotation-message behavior flags (issue #1230, CDK CLI parity).
595
611
  * Shared by `cdkd synth` and `cdkd deploy` — the two commands that fail on
596
612
  * `Annotations.addError` (issue #1228). `--strict` additionally fails when
@@ -622,6 +638,7 @@ const deployOptions = [
622
638
  replaceOption,
623
639
  useCdkBootstrapAssetsOption,
624
640
  strictGetattOption,
641
+ noCfnFallbackOption,
625
642
  ...resourceTimeoutOptions
626
643
  ];
627
644
  /**
@@ -4875,6 +4892,20 @@ function s3BucketDestinationPath(dest, destinationPath) {
4875
4892
  return isPlainObject$1(dest) && isFlattenedDestination(dest) ? destinationPath : `${destinationPath}.S3BucketDestination`;
4876
4893
  }
4877
4894
  /**
4895
+ * The structural path segments — relative to the enclosing `Destination` block —
4896
+ * of the bag `resolveS3BucketDestination` PICKED, so a substituted value can be
4897
+ * written back at the key the template actually declared (issue #1670).
4898
+ *
4899
+ * The value-side twin of {@link s3BucketDestinationPath}, which names the same
4900
+ * branch for MESSAGES. Derived from the picked bag's IDENTITY rather than by
4901
+ * re-running {@link isFlattenedDestination}: a third copy of that branch
4902
+ * condition is exactly what the predicate's own header warns about, and identity
4903
+ * cannot drift from the resolver at all.
4904
+ */
4905
+ function s3BucketDestinationSegments(declared, picked) {
4906
+ return picked === declared ? [] : ["S3BucketDestination"];
4907
+ }
4908
+ /**
4878
4909
  * Name a malformed value in a refusal message, the way `config-shape.ts`'s
4879
4910
  * private `describe` does for the string-reading guards. Kept local rather than
4880
4911
  * exported from there because `resolveS3BucketDestination` is S3-specific
@@ -5129,6 +5160,35 @@ var S3BucketProvider = class S3BucketProvider {
5129
5160
  return true;
5130
5161
  }
5131
5162
  /**
5163
+ * A string read whose replay downgrade is a warn-and-SUBSTITUTE, reporting
5164
+ * the substitution so the caller can record what was SENT (issue #1670).
5165
+ *
5166
+ * The third shape beside {@link skipOnUnusableConfigString}'s SKIP and the
5167
+ * plain strict read, for the sites where the fallback IS the right value to
5168
+ * send. The deploy then succeeds with the default on the wire, so recording
5169
+ * the declared value describes a configuration AWS does not hold, and
5170
+ * `readCurrentState` reads all of these back — the permanent phantom drift
5171
+ * `.claude/rules/providers.md` records for the #1633 warn-and-default arm.
5172
+ * That file also carries the full rationale, including why these sites take
5173
+ * NO `canonicalizeDesiredProperties` twin; do not re-derive it here.
5174
+ *
5175
+ * Three details are load-bearing:
5176
+ *
5177
+ * - `onSubstituted` receives the value this call RETURNS, not the `fallback`
5178
+ * argument, so "what is recorded" and "what is sent" stay one expression.
5179
+ * - `options` is forwarded to BOTH the probe and the read, so a site that
5180
+ * ever needs `coerceNumber` cannot make the two disagree.
5181
+ * - Without `options.onUnusable` there is no probe: the read still THROWS,
5182
+ * and recording a value the provider merely failed to send is what the
5183
+ * "already ANNOUNCED" condition on `effectiveProperties` forbids.
5184
+ */
5185
+ readSubstitutedConfigString(container, key, fallback, containerPath, options, onSubstituted) {
5186
+ const refused = options.onUnusable !== void 0 && configStringRefusal(container, key, fallback, containerPath, options) !== void 0;
5187
+ const value = readConfigString(container, key, fallback, containerPath, options);
5188
+ if (refused) onSubstituted(value);
5189
+ return value;
5190
+ }
5191
+ /**
5132
5192
  * Apply lifecycle configuration
5133
5193
  *
5134
5194
  * CFn property: LifecycleConfiguration.Rules[]
@@ -5457,8 +5517,11 @@ var S3BucketProvider = class S3BucketProvider {
5457
5517
  * CFn property: MetricsConfigurations[] (array of configurations)
5458
5518
  * SDK: PutBucketMetricsConfiguration (one per configuration, keyed by Id)
5459
5519
  *
5460
- * @returns the indexes (into `configs`) of the items a replay downgrade
5461
- * SKIPPED — the per-item unit of `effectiveProperties` (issue #1612).
5520
+ * @returns what happened to each item, as {@link PerItemApplyOutcome} the
5521
+ * per-item unit of `effectiveProperties` (issue #1612). This applier has no
5522
+ * warn-and-SUBSTITUTE read, so its `substituted` map is always empty; the
5523
+ * shape is shared with its three siblings so the two call-site recorders
5524
+ * stay uniform and a future substitution here cannot go unrecorded.
5462
5525
  */
5463
5526
  async applyMetricsConfigurations(bucketName, configs, onUnusable) {
5464
5527
  const skipped = [];
@@ -5490,7 +5553,10 @@ var S3BucketProvider = class S3BucketProvider {
5490
5553
  }));
5491
5554
  }
5492
5555
  this.logger.debug(`Applied ${configs.length - skipped.length} metrics configuration(s) to bucket ${bucketName}`);
5493
- return skipped;
5556
+ return {
5557
+ skipped,
5558
+ substituted: /* @__PURE__ */ new Map()
5559
+ };
5494
5560
  }
5495
5561
  /**
5496
5562
  * Pick the `S3BucketDestination` bag out of an analytics / inventory
@@ -5547,15 +5613,24 @@ var S3BucketProvider = class S3BucketProvider {
5547
5613
  * CFn property: AnalyticsConfigurations[] (array of configurations)
5548
5614
  * SDK: PutBucketAnalyticsConfiguration (one per configuration, keyed by Id)
5549
5615
  *
5550
- * @returns the indexes (into `configs`) of the items a replay downgrade
5551
- * SKIPPED — the per-item unit of `effectiveProperties` (issue #1612).
5616
+ * @returns what happened to each item, as {@link PerItemApplyOutcome} the
5617
+ * per-item unit of `effectiveProperties` (issues #1612 / #1670). Both arms
5618
+ * are reachable here: a container / destination guard SKIPS the item, while
5619
+ * the `OutputSchemaVersion` and destination `Format` reads warn and SEND a
5620
+ * substituted default, so the item is recorded as SENT rather than as
5621
+ * declared.
5552
5622
  */
5553
5623
  async applyAnalyticsConfigurations(bucketName, configs, onUnusable) {
5554
5624
  const skipped = [];
5625
+ const substituted = /* @__PURE__ */ new Map();
5555
5626
  let index = -1;
5556
5627
  for (const config of configs) {
5557
5628
  index += 1;
5558
5629
  const id = config["Id"];
5630
+ let sentItem;
5631
+ const recordSubstitution = (path, sent) => {
5632
+ sentItem = S3BucketProvider.withDeepValue(sentItem ?? config, path, sent);
5633
+ };
5559
5634
  const rawStorageClassAnalysis = config["StorageClassAnalysis"];
5560
5635
  if (rawStorageClassAnalysis != null && requireConfigObject(rawStorageClassAnalysis, "AWS::S3::Bucket AnalyticsConfigurations[].StorageClassAnalysis", { ...onUnusable ? { onUnusable } : {} }) === void 0) {
5561
5636
  skipped.push(index);
@@ -5594,12 +5669,23 @@ var S3BucketProvider = class S3BucketProvider {
5594
5669
  continue;
5595
5670
  }
5596
5671
  const destPath = s3BucketDestinationPath(rawDest, analyticsDestPath);
5672
+ const destSegments = s3Dest ? s3BucketDestinationSegments(rawDest, s3Dest) : [];
5597
5673
  analyticsConfig.StorageClassAnalysis = { DataExport: {
5598
- OutputSchemaVersion: readConfigString(dataExport, "OutputSchemaVersion", "V_1", "AWS::S3::Bucket AnalyticsConfigurations[].StorageClassAnalysis.DataExport", { ...onUnusable ? { onUnusable } : {} }),
5674
+ OutputSchemaVersion: this.readSubstitutedConfigString(dataExport, "OutputSchemaVersion", "V_1", "AWS::S3::Bucket AnalyticsConfigurations[].StorageClassAnalysis.DataExport", { ...onUnusable ? { onUnusable } : {} }, (sent) => recordSubstitution([
5675
+ "StorageClassAnalysis",
5676
+ "DataExport",
5677
+ "OutputSchemaVersion"
5678
+ ], sent)),
5599
5679
  Destination: s3Dest ? { S3BucketDestination: {
5600
5680
  Bucket: s3Dest["BucketArn"] ?? s3Dest["Bucket"],
5601
5681
  BucketAccountId: s3Dest["BucketAccountId"],
5602
- Format: readConfigString(s3Dest, "Format", "CSV", destPath, { ...onUnusable ? { onUnusable } : {} }),
5682
+ Format: this.readSubstitutedConfigString(s3Dest, "Format", "CSV", destPath, { ...onUnusable ? { onUnusable } : {} }, (sent) => recordSubstitution([
5683
+ "StorageClassAnalysis",
5684
+ "DataExport",
5685
+ "Destination",
5686
+ ...destSegments,
5687
+ "Format"
5688
+ ], sent)),
5603
5689
  Prefix: s3Dest["Prefix"]
5604
5690
  } } : void 0
5605
5691
  } };
@@ -5609,9 +5695,13 @@ var S3BucketProvider = class S3BucketProvider {
5609
5695
  Id: id,
5610
5696
  AnalyticsConfiguration: analyticsConfig
5611
5697
  }));
5698
+ if (sentItem !== void 0) substituted.set(index, sentItem);
5612
5699
  }
5613
5700
  this.logger.debug(`Applied ${configs.length - skipped.length} analytics configuration(s) to bucket ${bucketName}`);
5614
- return skipped;
5701
+ return {
5702
+ skipped,
5703
+ substituted
5704
+ };
5615
5705
  }
5616
5706
  /**
5617
5707
  * Apply intelligent tiering configurations
@@ -5619,8 +5709,11 @@ var S3BucketProvider = class S3BucketProvider {
5619
5709
  * CFn property: IntelligentTieringConfigurations[]
5620
5710
  * SDK: PutBucketIntelligentTieringConfiguration (one per configuration, keyed by Id)
5621
5711
  *
5622
- * @returns the indexes (into `configs`) of the items a replay downgrade
5623
- * SKIPPED — the per-item unit of `effectiveProperties` (issue #1612).
5712
+ * @returns what happened to each item, as {@link PerItemApplyOutcome} the
5713
+ * per-item unit of `effectiveProperties` (issue #1612). Its per-item
5714
+ * `Status` read takes the SKIP downgrade rather than a substitution (issue
5715
+ * #1595 — defaulting to `Enabled` would start archiving objects), so the
5716
+ * `substituted` map is always empty here.
5624
5717
  */
5625
5718
  async applyIntelligentTieringConfigurations(bucketName, configs, onUnusable) {
5626
5719
  const skipped = [];
@@ -5661,7 +5754,10 @@ var S3BucketProvider = class S3BucketProvider {
5661
5754
  }));
5662
5755
  }
5663
5756
  this.logger.debug(`Applied ${configs.length - skipped.length} intelligent tiering configuration(s) to bucket ${bucketName}`);
5664
- return skipped;
5757
+ return {
5758
+ skipped,
5759
+ substituted: /* @__PURE__ */ new Map()
5760
+ };
5665
5761
  }
5666
5762
  /**
5667
5763
  * Apply inventory configurations
@@ -5669,11 +5765,15 @@ var S3BucketProvider = class S3BucketProvider {
5669
5765
  * CFn property: InventoryConfigurations[]
5670
5766
  * SDK: PutBucketInventoryConfiguration (one per configuration, keyed by Id)
5671
5767
  *
5672
- * @returns the indexes (into `configs`) of the items a replay downgrade
5673
- * SKIPPED — the per-item unit of `effectiveProperties` (issue #1612).
5768
+ * @returns what happened to each item, as {@link PerItemApplyOutcome} the
5769
+ * per-item unit of `effectiveProperties` (issues #1612 / #1670). Both arms
5770
+ * are reachable here: `IncludedObjectVersions` / the schedule frequency pair
5771
+ * / the destination guard SKIP the item, while the destination `Format` read
5772
+ * warns and SENDS a substituted default.
5674
5773
  */
5675
5774
  async applyInventoryConfigurations(bucketName, configs, onUnusable) {
5676
5775
  const skipped = [];
5776
+ const substituted = /* @__PURE__ */ new Map();
5677
5777
  let index = -1;
5678
5778
  for (const config of configs) {
5679
5779
  index += 1;
@@ -5690,6 +5790,11 @@ var S3BucketProvider = class S3BucketProvider {
5690
5790
  continue;
5691
5791
  }
5692
5792
  const destPath = s3BucketDestinationPath(rawDest, inventoryDestPath);
5793
+ const destSegments = s3Dest ? s3BucketDestinationSegments(rawDest, s3Dest) : [];
5794
+ let sentItem;
5795
+ const recordSubstitution = (path, sent) => {
5796
+ sentItem = S3BucketProvider.withDeepValue(sentItem ?? config, path, sent);
5797
+ };
5693
5798
  const scheduleFrequencyRefusal = onUnusable ? configStringRefusal(config, "ScheduleFrequency", "Weekly", "AWS::S3::Bucket InventoryConfigurations[]") : void 0;
5694
5799
  if (scheduleFrequencyRefusal !== void 0 && config["Schedule"] == null) {
5695
5800
  onUnusable?.(`${scheduleFrequencyRefusal}. Skipping this inventory configuration: there is no Schedule.Frequency to fall back to, and defaulting to Weekly would silently re-cadence a live inventory report; the same value is REFUSED on a template-path create`);
@@ -5703,6 +5808,7 @@ var S3BucketProvider = class S3BucketProvider {
5703
5808
  continue;
5704
5809
  }
5705
5810
  const frequency = useScheduleFrequency ? requireConfigString(config["ScheduleFrequency"], "Weekly", "AWS::S3::Bucket InventoryConfigurations[].ScheduleFrequency") : readConfigString(config["Schedule"], "Frequency", "Weekly", "AWS::S3::Bucket InventoryConfigurations[].Schedule");
5811
+ if (scheduleFrequencyRefusal !== void 0) recordSubstitution(["ScheduleFrequency"], frequency);
5706
5812
  const inventoryConfig = {
5707
5813
  Id: id,
5708
5814
  IsEnabled: config["Enabled"] ?? true,
@@ -5711,7 +5817,11 @@ var S3BucketProvider = class S3BucketProvider {
5711
5817
  Destination: { S3BucketDestination: s3Dest ? {
5712
5818
  Bucket: s3Dest["BucketArn"] ?? s3Dest["Bucket"],
5713
5819
  AccountId: s3Dest["BucketAccountId"],
5714
- Format: readConfigString(s3Dest, "Format", "CSV", destPath, { ...onUnusable ? { onUnusable } : {} }),
5820
+ Format: this.readSubstitutedConfigString(s3Dest, "Format", "CSV", destPath, { ...onUnusable ? { onUnusable } : {} }, (sent) => recordSubstitution([
5821
+ "Destination",
5822
+ ...destSegments,
5823
+ "Format"
5824
+ ], sent)),
5715
5825
  Prefix: s3Dest["Prefix"]
5716
5826
  } : void 0 },
5717
5827
  OptionalFields: config["OptionalFields"],
@@ -5722,9 +5832,13 @@ var S3BucketProvider = class S3BucketProvider {
5722
5832
  Id: id,
5723
5833
  InventoryConfiguration: inventoryConfig
5724
5834
  }));
5835
+ if (sentItem !== void 0) substituted.set(index, sentItem);
5725
5836
  }
5726
5837
  this.logger.debug(`Applied ${configs.length - skipped.length} inventory configuration(s) to bucket ${bucketName}`);
5727
- return skipped;
5838
+ return {
5839
+ skipped,
5840
+ substituted
5841
+ };
5728
5842
  }
5729
5843
  /**
5730
5844
  * Apply replication configuration
@@ -5958,24 +6072,26 @@ var S3BucketProvider = class S3BucketProvider {
5958
6072
  return config;
5959
6073
  }
5960
6074
  /**
5961
- * Turn a set of skipped-property overrides into the `effectiveProperties`
5962
- * bag the deploy engine records in place of the desired one (issue #1612).
6075
+ * Turn a set of per-property overrides into the `effectiveProperties` bag the
6076
+ * deploy engine records in place of the desired one (issues #1612 / #1670).
5963
6077
  *
5964
- * A warn-and-skip arm makes the deploy SUCCEED while a declared
5965
- * configuration never reaches AWS, so recording the desired bag verbatim
5966
- * writes a value AWS does not hold: `readCurrentState` can never match it,
5967
- * every later `cdkd drift` re-reports the difference, and `drift --revert`
5968
- * re-issues the same skipped call the permanent phantom drift
5969
- * `.claude/rules/providers.md` records for the EC2 Route warn arm (#1591),
5970
- * reached here through a skip instead of a narrowing.
6078
+ * A warn-and-SKIP arm makes the deploy SUCCEED while a declared configuration
6079
+ * never reaches AWS, and a warn-and-SUBSTITUTE read makes it succeed while a
6080
+ * DIFFERENT value than the declared one reaches AWS. Either way, recording
6081
+ * the desired bag verbatim writes something AWS does not hold:
6082
+ * `readCurrentState` can never match it, every later `cdkd drift` re-reports
6083
+ * the difference, and `drift --revert` re-issues the same call the
6084
+ * permanent phantom drift `.claude/rules/providers.md` records for the EC2
6085
+ * Route warn arm (#1591), reached here through a skip or a substitution
6086
+ * instead of a narrowing.
5971
6087
  *
5972
6088
  * A `undefined` override value means the key is ABSENT from the effective
5973
6089
  * bag, which is why this walks the map rather than spreading it: spreading
5974
6090
  * would leave an explicit `undefined` that `JSON.stringify` drops on one
5975
6091
  * side of the next diff but not the other.
5976
6092
  *
5977
- * @returns the complete replacement bag, or `undefined` when nothing was
5978
- * skipped — the engine then records the desired properties unchanged.
6093
+ * @returns the complete replacement bag, or `undefined` when there is nothing
6094
+ * to override — the engine then records the desired properties unchanged.
5979
6095
  */
5980
6096
  static applyEffectiveOverrides(properties, overrides) {
5981
6097
  if (overrides.size === 0) return void 0;
@@ -5999,14 +6115,22 @@ var S3BucketProvider = class S3BucketProvider {
5999
6115
  * items would manufacture a fresh phantom drift while removing the one this
6000
6116
  * exists to fix.
6001
6117
  *
6118
+ * An item that WAS applied but with a value a warn-and-SUBSTITUTE read
6119
+ * replaced is the third arm (issue #1670): it keeps its position and its
6120
+ * declared shape, and only the substituted field differs — `sentItem` is the
6121
+ * item AS SENT, which is what AWS holds and therefore what state must
6122
+ * describe.
6123
+ *
6002
6124
  * @param previous the previously-recorded array, or `undefined` on the
6003
6125
  * replay-CREATE path, where nothing was ever applied and there is no
6004
6126
  * previous item to substitute.
6127
+ * @param sentItemOf the item as SENT for an APPLIED item whose declared value
6128
+ * was substituted, or `undefined` when declared == sent (the ordinary case).
6005
6129
  * @returns the effective array, or `undefined` when the key should be ABSENT
6006
6130
  * from the effective bag (nothing survived AND the previous side declared
6007
6131
  * nothing, so the property was never applied at all).
6008
6132
  */
6009
- static buildEffectiveItemArray(desired, previous, isSkipped) {
6133
+ static buildEffectiveItemArray(desired, previous, isSkipped, sentItemOf) {
6010
6134
  const previousById = /* @__PURE__ */ new Map();
6011
6135
  for (const item of previous ?? []) {
6012
6136
  const id = configItemId(item);
@@ -6015,7 +6139,7 @@ var S3BucketProvider = class S3BucketProvider {
6015
6139
  const effective = [];
6016
6140
  desired.forEach((item, index) => {
6017
6141
  if (!isSkipped(item, index)) {
6018
- effective.push(item);
6142
+ effective.push(sentItemOf?.(item, index) ?? item);
6019
6143
  return;
6020
6144
  }
6021
6145
  const id = configItemId(item);
@@ -6026,6 +6150,34 @@ var S3BucketProvider = class S3BucketProvider {
6026
6150
  return effective;
6027
6151
  }
6028
6152
  /**
6153
+ * A COPY of `container` with `path` set to `value` (issue #1670).
6154
+ *
6155
+ * Copying rather than mutating is load-bearing, not hygiene: the container is
6156
+ * an item of the caller's DESIRED property bag, which `DiffCalculator` and the
6157
+ * engine still read afterwards — writing the substituted value into it would
6158
+ * silently rewrite the template side of the next comparison, which is exactly
6159
+ * the `canonicalizeDesiredProperties` behavior this fix must NOT have. Only
6160
+ * the objects ALONG the path are cloned; every sibling subtree is shared,
6161
+ * which is safe because nothing here mutates them.
6162
+ *
6163
+ * A non-object intermediate is rebuilt as an EMPTY block, which is not a
6164
+ * defensive shrug but the accurate record: it is what `readConfigString`
6165
+ * itself did on the way in — its container arm says "Treating the block as
6166
+ * empty, so <path>.<key> takes its default" — so `{}` plus the substituted
6167
+ * leaf IS what was sent. No call site can reach it today (every intermediate
6168
+ * has already been validated by `requireConfigObject` /
6169
+ * `resolveS3BucketDestination`, which SKIP the item on a malformed one).
6170
+ */
6171
+ static withDeepValue(container, path, value) {
6172
+ const [head, ...rest] = path;
6173
+ if (head === void 0) return container;
6174
+ const child = container[head];
6175
+ return {
6176
+ ...container,
6177
+ [head]: rest.length === 0 ? value : S3BucketProvider.withDeepValue(isPlainObject$1(child) ? child : {}, rest, value)
6178
+ };
6179
+ }
6180
+ /**
6029
6181
  * Diff CFn-shape sub-config values between previous and new state.
6030
6182
  *
6031
6183
  * Four transitions:
@@ -6100,18 +6252,42 @@ var S3BucketProvider = class S3BucketProvider {
6100
6252
  const retainPrevious = (key) => {
6101
6253
  overrides.set(key, previousProperties[key]);
6102
6254
  };
6103
- /** Record a PER-ITEM skip set against the desired / previous arrays. */
6104
- const retainPreviousItems = (key, skippedIds) => {
6105
- if (skippedIds.size === 0) return;
6255
+ /**
6256
+ * Per-property accumulator for one of the four `Id`-keyed appliers.
6257
+ *
6258
+ * Keyed by `Id` rather than by index because on THIS path the diff loop
6259
+ * hands the applier exactly one item per call (so every outcome index is
6260
+ * 0) and the id is all the loop knows about it.
6261
+ */
6262
+ const itemOutcomes = () => {
6263
+ const skipped = /* @__PURE__ */ new Set();
6264
+ const sentItems = /* @__PURE__ */ new Map();
6265
+ return {
6266
+ skipped,
6267
+ sentItems,
6268
+ record: (id, outcome) => {
6269
+ if (outcome.skipped.length > 0) skipped.add(id);
6270
+ const sent = outcome.substituted.get(0);
6271
+ if (sent !== void 0) sentItems.set(id, sent);
6272
+ }
6273
+ };
6274
+ };
6275
+ /** Record a PER-ITEM skip / substitution set against the desired / previous arrays. */
6276
+ const retainPreviousItems = (key, collected) => {
6277
+ const { skipped: skippedIds, sentItems } = collected;
6278
+ if (skippedIds.size === 0 && sentItems.size === 0) return;
6106
6279
  const desired = properties[key];
6107
6280
  if (!Array.isArray(desired)) {
6108
- this.logger.debug(`Bucket ${bucketName}: ${skippedIds.size} skipped ${key} item(s) could not be recorded — the desired value is not an array`);
6281
+ this.logger.debug(`Bucket ${bucketName}: ${skippedIds.size + sentItems.size} skipped or substituted ${key} item(s) could not be recorded — the desired value is not an array`);
6109
6282
  return;
6110
6283
  }
6111
6284
  const previous = previousProperties[key];
6112
6285
  overrides.set(key, S3BucketProvider.buildEffectiveItemArray(desired, Array.isArray(previous) ? previous : void 0, (item) => {
6113
6286
  const id = configItemId(item);
6114
6287
  return id !== void 0 && skippedIds.has(id);
6288
+ }, (item) => {
6289
+ const id = configItemId(item);
6290
+ return id !== void 0 ? sentItems.get(id) : void 0;
6115
6291
  }));
6116
6292
  };
6117
6293
  const previousVersioning = previousProperties["VersioningConfiguration"];
@@ -6172,9 +6348,10 @@ var S3BucketProvider = class S3BucketProvider {
6172
6348
  this.logger.debug(`Cleared object lock rule on bucket ${bucketName}`);
6173
6349
  });
6174
6350
  await this.diffSubConfig(bucketName, previousProperties["AccelerateConfiguration"], properties["AccelerateConfiguration"], async (cfg) => this.applyAccelerateConfiguration(bucketName, cfg), async () => this.applyAccelerateConfiguration(bucketName, { AccelerationStatus: "Suspended" }));
6175
- const skippedMetricsIds = /* @__PURE__ */ new Set();
6351
+ const metricsOutcomes = itemOutcomes();
6176
6352
  await this.diffArrayConfigById(bucketName, previousProperties["MetricsConfigurations"], properties["MetricsConfigurations"], async (id, cfg) => {
6177
- if ((await this.applyMetricsConfigurations(bucketName, [cfg], (m) => this.logger.warn(m))).length > 0) skippedMetricsIds.add(String(id));
6353
+ const outcome = await this.applyMetricsConfigurations(bucketName, [cfg], (m) => this.logger.warn(m));
6354
+ metricsOutcomes.record(String(id), outcome);
6178
6355
  }, async (id) => {
6179
6356
  await this.s3Client.send(new DeleteBucketMetricsConfigurationCommand({
6180
6357
  Bucket: bucketName,
@@ -6182,10 +6359,11 @@ var S3BucketProvider = class S3BucketProvider {
6182
6359
  }));
6183
6360
  this.logger.debug(`Deleted metrics configuration ${id} on bucket ${bucketName}`);
6184
6361
  });
6185
- retainPreviousItems("MetricsConfigurations", skippedMetricsIds);
6186
- const skippedAnalyticsIds = /* @__PURE__ */ new Set();
6362
+ retainPreviousItems("MetricsConfigurations", metricsOutcomes);
6363
+ const analyticsOutcomes = itemOutcomes();
6187
6364
  await this.diffArrayConfigById(bucketName, previousProperties["AnalyticsConfigurations"], properties["AnalyticsConfigurations"], async (id, cfg) => {
6188
- if ((await this.applyAnalyticsConfigurations(bucketName, [cfg], (m) => this.logger.warn(m))).length > 0) skippedAnalyticsIds.add(String(id));
6365
+ const outcome = await this.applyAnalyticsConfigurations(bucketName, [cfg], (m) => this.logger.warn(m));
6366
+ analyticsOutcomes.record(String(id), outcome);
6189
6367
  }, async (id) => {
6190
6368
  await this.s3Client.send(new DeleteBucketAnalyticsConfigurationCommand({
6191
6369
  Bucket: bucketName,
@@ -6193,10 +6371,11 @@ var S3BucketProvider = class S3BucketProvider {
6193
6371
  }));
6194
6372
  this.logger.debug(`Deleted analytics configuration ${id} on bucket ${bucketName}`);
6195
6373
  });
6196
- retainPreviousItems("AnalyticsConfigurations", skippedAnalyticsIds);
6197
- const skippedIntelligentTieringIds = /* @__PURE__ */ new Set();
6374
+ retainPreviousItems("AnalyticsConfigurations", analyticsOutcomes);
6375
+ const intelligentTieringOutcomes = itemOutcomes();
6198
6376
  await this.diffArrayConfigById(bucketName, previousProperties["IntelligentTieringConfigurations"], properties["IntelligentTieringConfigurations"], async (id, cfg) => {
6199
- if ((await this.applyIntelligentTieringConfigurations(bucketName, [cfg], (m) => this.logger.warn(m))).length > 0) skippedIntelligentTieringIds.add(String(id));
6377
+ const outcome = await this.applyIntelligentTieringConfigurations(bucketName, [cfg], (m) => this.logger.warn(m));
6378
+ intelligentTieringOutcomes.record(String(id), outcome);
6200
6379
  }, async (id) => {
6201
6380
  await this.s3Client.send(new DeleteBucketIntelligentTieringConfigurationCommand({
6202
6381
  Bucket: bucketName,
@@ -6204,10 +6383,11 @@ var S3BucketProvider = class S3BucketProvider {
6204
6383
  }));
6205
6384
  this.logger.debug(`Deleted intelligent tiering configuration ${id} on bucket ${bucketName}`);
6206
6385
  });
6207
- retainPreviousItems("IntelligentTieringConfigurations", skippedIntelligentTieringIds);
6208
- const skippedInventoryIds = /* @__PURE__ */ new Set();
6386
+ retainPreviousItems("IntelligentTieringConfigurations", intelligentTieringOutcomes);
6387
+ const inventoryOutcomes = itemOutcomes();
6209
6388
  await this.diffArrayConfigById(bucketName, previousProperties["InventoryConfigurations"], properties["InventoryConfigurations"], async (id, cfg) => {
6210
- if ((await this.applyInventoryConfigurations(bucketName, [cfg], (m) => this.logger.warn(m))).length > 0) skippedInventoryIds.add(String(id));
6389
+ const outcome = await this.applyInventoryConfigurations(bucketName, [cfg], (m) => this.logger.warn(m));
6390
+ inventoryOutcomes.record(String(id), outcome);
6211
6391
  }, async (id) => {
6212
6392
  await this.s3Client.send(new DeleteBucketInventoryConfigurationCommand({
6213
6393
  Bucket: bucketName,
@@ -6215,7 +6395,7 @@ var S3BucketProvider = class S3BucketProvider {
6215
6395
  }));
6216
6396
  this.logger.debug(`Deleted inventory configuration ${id} on bucket ${bucketName}`);
6217
6397
  });
6218
- retainPreviousItems("InventoryConfigurations", skippedInventoryIds);
6398
+ retainPreviousItems("InventoryConfigurations", inventoryOutcomes);
6219
6399
  if (versioningChanged && desiredVersioningStatus === "Suspended") {
6220
6400
  const hasObjectLock = (p) => {
6221
6401
  if (p["ObjectLockEnabled"] === true || p["ObjectLockEnabled"] === "true") return true;
@@ -6232,20 +6412,29 @@ var S3BucketProvider = class S3BucketProvider {
6232
6412
  * Apply ALL sub-configs unconditionally on initial create. Used by
6233
6413
  * `create()` so the bucket starts out matching the template.
6234
6414
  *
6235
- * @returns the per-property overrides for `effectiveProperties` (issue
6236
- * #1612). Every skip reachable here is on the REPLAY-create path (the
6237
- * rollback executor's reverse-replacement arm), where the bucket is brand
6238
- * new and nothing was applied — so the override DROPS the key rather than
6239
- * retaining a previous value, because there is no previous value to keep.
6240
- * The per-item arrays keep the items that DID apply.
6415
+ * @returns the per-property overrides for `effectiveProperties` (issues
6416
+ * #1612 / #1670). Every downgrade reachable here is on the REPLAY-create
6417
+ * path (the rollback executor's reverse-replacement arm), where the bucket
6418
+ * is brand new and nothing was applied — so a SKIP drops the key rather
6419
+ * than retaining a previous value, because there is no previous value to
6420
+ * keep. The per-item arrays keep the items that DID apply, including an
6421
+ * item applied with a SUBSTITUTED value, recorded as it was sent.
6241
6422
  */
6242
6423
  async applyAllSubConfigsForCreate(bucketName, properties, context) {
6243
6424
  const overrides = /* @__PURE__ */ new Map();
6244
- /** Record a per-ITEM skip set: nothing to substitute on a fresh bucket. */
6245
- const dropSkippedItems = (key, configs, skippedIndexes) => {
6246
- if (skippedIndexes.length === 0) return;
6247
- const skipped = new Set(skippedIndexes);
6248
- overrides.set(key, S3BucketProvider.buildEffectiveItemArray(configs, void 0, (_item, index) => skipped.has(index)));
6425
+ /**
6426
+ * Record a per-ITEM applier outcome.
6427
+ *
6428
+ * A SKIPPED item is DROPPED — there is no previously-applied item to
6429
+ * substitute on a fresh bucket. An item applied with a SUBSTITUTED value
6430
+ * (issue #1670) is kept in place as SENT, which is a decision the create
6431
+ * path makes for the same reason the update path does: the Put SUCCEEDED,
6432
+ * so AWS holds the substituted value and state must say so.
6433
+ */
6434
+ const recordItemOutcome = (key, configs, outcome) => {
6435
+ if (outcome.skipped.length === 0 && outcome.substituted.size === 0) return;
6436
+ const skipped = new Set(outcome.skipped);
6437
+ overrides.set(key, S3BucketProvider.buildEffectiveItemArray(configs, void 0, (_item, index) => skipped.has(index), (_item, index) => outcome.substituted.get(index)));
6249
6438
  };
6250
6439
  const replayOnUnusable = replayWarn(this.logger, context).onUnusable;
6251
6440
  const notifConfig = properties["NotificationConfiguration"];
@@ -6269,13 +6458,13 @@ var S3BucketProvider = class S3BucketProvider {
6269
6458
  const accelerateConfig = properties["AccelerateConfiguration"];
6270
6459
  if (accelerateConfig) await this.applyAccelerateConfiguration(bucketName, accelerateConfig);
6271
6460
  const metricsConfigs = properties["MetricsConfigurations"];
6272
- if (metricsConfigs && Array.isArray(metricsConfigs) && metricsConfigs.length > 0) dropSkippedItems("MetricsConfigurations", metricsConfigs, await this.applyMetricsConfigurations(bucketName, metricsConfigs, replayOnUnusable));
6461
+ if (metricsConfigs && Array.isArray(metricsConfigs) && metricsConfigs.length > 0) recordItemOutcome("MetricsConfigurations", metricsConfigs, await this.applyMetricsConfigurations(bucketName, metricsConfigs, replayOnUnusable));
6273
6462
  const analyticsConfigs = properties["AnalyticsConfigurations"];
6274
- if (analyticsConfigs && Array.isArray(analyticsConfigs) && analyticsConfigs.length > 0) dropSkippedItems("AnalyticsConfigurations", analyticsConfigs, await this.applyAnalyticsConfigurations(bucketName, analyticsConfigs, replayOnUnusable));
6463
+ if (analyticsConfigs && Array.isArray(analyticsConfigs) && analyticsConfigs.length > 0) recordItemOutcome("AnalyticsConfigurations", analyticsConfigs, await this.applyAnalyticsConfigurations(bucketName, analyticsConfigs, replayOnUnusable));
6275
6464
  const itConfigs = properties["IntelligentTieringConfigurations"];
6276
- if (itConfigs && Array.isArray(itConfigs) && itConfigs.length > 0) dropSkippedItems("IntelligentTieringConfigurations", itConfigs, await this.applyIntelligentTieringConfigurations(bucketName, itConfigs, replayOnUnusable));
6465
+ if (itConfigs && Array.isArray(itConfigs) && itConfigs.length > 0) recordItemOutcome("IntelligentTieringConfigurations", itConfigs, await this.applyIntelligentTieringConfigurations(bucketName, itConfigs, replayOnUnusable));
6277
6466
  const inventoryConfigs = properties["InventoryConfigurations"];
6278
- if (inventoryConfigs && Array.isArray(inventoryConfigs) && inventoryConfigs.length > 0) dropSkippedItems("InventoryConfigurations", inventoryConfigs, await this.applyInventoryConfigurations(bucketName, inventoryConfigs, replayOnUnusable));
6467
+ if (inventoryConfigs && Array.isArray(inventoryConfigs) && inventoryConfigs.length > 0) recordItemOutcome("InventoryConfigurations", inventoryConfigs, await this.applyInventoryConfigurations(bucketName, inventoryConfigs, replayOnUnusable));
6279
6468
  const replConfig = properties["ReplicationConfiguration"];
6280
6469
  if (replConfig) {
6281
6470
  if (!await this.applyReplicationConfiguration(bucketName, replConfig, replayOnUnusable)) overrides.set("ReplicationConfiguration", void 0);
@@ -6866,15 +7055,17 @@ var S3BucketProvider = class S3BucketProvider {
6866
7055
  if (sca?.["DataExport"]) {
6867
7056
  const dataExport = sca["DataExport"];
6868
7057
  const s3Dest = dataExport["Destination"]?.["S3BucketDestination"];
6869
- out["StorageClassAnalysis"] = { DataExport: {
6870
- OutputSchemaVersion: dataExport["OutputSchemaVersion"],
6871
- Destination: s3Dest ? { S3BucketDestination: {
6872
- BucketArn: s3Dest["Bucket"],
6873
- BucketAccountId: s3Dest["BucketAccountId"],
6874
- Format: s3Dest["Format"],
6875
- Prefix: s3Dest["Prefix"]
6876
- } } : void 0
6877
- } };
7058
+ const dataExportCfn = {};
7059
+ if (dataExport["OutputSchemaVersion"] !== void 0) dataExportCfn["OutputSchemaVersion"] = dataExport["OutputSchemaVersion"];
7060
+ if (s3Dest) {
7061
+ const cfnDest = {};
7062
+ if (s3Dest["Bucket"] !== void 0) cfnDest["BucketArn"] = s3Dest["Bucket"];
7063
+ if (s3Dest["BucketAccountId"] !== void 0) cfnDest["BucketAccountId"] = s3Dest["BucketAccountId"];
7064
+ if (s3Dest["Format"] !== void 0) cfnDest["Format"] = s3Dest["Format"];
7065
+ if (s3Dest["Prefix"] !== void 0) cfnDest["Prefix"] = s3Dest["Prefix"];
7066
+ dataExportCfn["Destination"] = cfnDest;
7067
+ }
7068
+ out["StorageClassAnalysis"] = { DataExport: dataExportCfn };
6878
7069
  }
6879
7070
  return out;
6880
7071
  }
@@ -51641,6 +51832,7 @@ async function deployCommand(stacks, options) {
51641
51832
  ...options.skipFinalSnapshot && { skipFinalSnapshot: true },
51642
51833
  finalSnapshotClients: stackAwsClients,
51643
51834
  ...options.strictGetatt && { strictGetAtt: true },
51835
+ ...options.cfnFallback === false && { cfnFallback: false },
51644
51836
  captureObservedState: resolveCaptureObservedState(options.captureObservedState),
51645
51837
  ...options.resourceWarnAfter?.globalMs !== void 0 && { resourceWarnAfterMs: options.resourceWarnAfter.globalMs },
51646
51838
  ...options.resourceTimeout?.globalMs !== void 0 && { resourceTimeoutMs: options.resourceTimeout.globalMs },
@@ -52702,8 +52894,8 @@ async function loadStateOrEmpty(stackName, region, stateBackend) {
52702
52894
  * Pure with respect to AWS state mutation — only reads state (the resolver
52703
52895
  * may read producer state for `Fn::ImportValue` / `Fn::GetStackOutput`).
52704
52896
  */
52705
- async function computeStackDiff(currentState, template, region, stackName, stateBackend, diffCalculator, parameters, canonicalizeProperties) {
52706
- const intrinsicResolver = new IntrinsicFunctionResolver(region);
52897
+ async function computeStackDiff(currentState, template, region, stackName, stateBackend, diffCalculator, parameters, canonicalizeProperties, cfnFallback) {
52898
+ const intrinsicResolver = new IntrinsicFunctionResolver(region, { cfnFallback: cfnFallback ?? true });
52707
52899
  let mergedParameters = parameters;
52708
52900
  let parametersBound = false;
52709
52901
  try {
@@ -52765,10 +52957,10 @@ async function computeStackDiff(currentState, template, region, stackName, state
52765
52957
  * unresolvable case), while resolvable parameters (the freshly-deployed tree)
52766
52958
  * get exact scalar values.
52767
52959
  */
52768
- async function resolveChildStackParameters(parentStackRow, parentTemplate, parentState, region, parentStackName, stateBackend, parentParameters) {
52960
+ async function resolveChildStackParameters(parentStackRow, parentTemplate, parentState, region, parentStackName, stateBackend, parentParameters, cfnFallback) {
52769
52961
  const rawParams = parentStackRow.Properties?.["Parameters"];
52770
52962
  if (!rawParams || typeof rawParams !== "object" || Array.isArray(rawParams)) return {};
52771
- const resolver = new IntrinsicFunctionResolver(region);
52963
+ const resolver = new IntrinsicFunctionResolver(region, { cfnFallback: cfnFallback ?? true });
52772
52964
  const resolved = {};
52773
52965
  for (const [name, value] of Object.entries(rawParams)) try {
52774
52966
  resolved[name] = await resolver.resolve(value, {
@@ -52803,13 +52995,13 @@ async function resolveChildStackParameters(parentStackRow, parentTemplate, paren
52803
52995
  * fail at deploy time.
52804
52996
  */
52805
52997
  async function buildDiffTree(args) {
52806
- const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters, canonicalizeProperties, assetRedirect } = args;
52998
+ const { stackName, displayName, region, template, nestedTemplates, recursive, stateBackend, diffCalculator, parameters, canonicalizeProperties, assetRedirect, cfnFallback } = args;
52807
52999
  const state = await loadStateOrEmpty(stackName, region, stateBackend);
52808
53000
  const node = {
52809
53001
  stackName,
52810
53002
  displayName,
52811
53003
  region,
52812
- changes: await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, parameters, canonicalizeProperties),
53004
+ changes: await computeStackDiff(state, template, region, stackName, stateBackend, diffCalculator, parameters, canonicalizeProperties, cfnFallback),
52813
53005
  ccApiRoutes: collectCcApiRoutes(template, state),
52814
53006
  children: []
52815
53007
  };
@@ -52824,7 +53016,7 @@ async function buildDiffTree(args) {
52824
53016
  const childTemplate = readNestedTemplate(childTemplatePath);
52825
53017
  if (assetRedirect) rewriteTemplateAssetReferences(childTemplate, assetRedirect);
52826
53018
  const grandchildTemplates = indexNestedChildTemplates(childTemplate, childTemplatePath);
52827
- const childParameters = await resolveChildStackParameters(resource, template, state, region, stackName, stateBackend, parameters);
53019
+ const childParameters = await resolveChildStackParameters(resource, template, state, region, stackName, stateBackend, parameters, cfnFallback);
52828
53020
  node.children.push(await buildDiffTree({
52829
53021
  stackName: childStackName,
52830
53022
  displayName: childStackName,
@@ -52836,7 +53028,8 @@ async function buildDiffTree(args) {
52836
53028
  diffCalculator,
52837
53029
  parameters: childParameters,
52838
53030
  ...canonicalizeProperties && { canonicalizeProperties },
52839
- ...assetRedirect && { assetRedirect }
53031
+ ...assetRedirect && { assetRedirect },
53032
+ ...cfnFallback !== void 0 && { cfnFallback }
52840
53033
  }));
52841
53034
  }
52842
53035
  for (const [logicalId, resource] of Object.entries(state.resources)) {
@@ -53223,7 +53416,8 @@ async function diffCommand(stacks, options) {
53223
53416
  stateBackend,
53224
53417
  diffCalculator,
53225
53418
  canonicalizeProperties,
53226
- ...assetRedirect && { assetRedirect }
53419
+ ...assetRedirect && { assetRedirect },
53420
+ ...options.cfnFallback === false && { cfnFallback: false }
53227
53421
  }));
53228
53422
  }
53229
53423
  if (options.json) process.stdout.write(`${JSON.stringify(trees.map(diffTreeToJson), null, 2)}\n`);
@@ -53243,7 +53437,7 @@ async function diffCommand(stacks, options) {
53243
53437
  * Create diff command
53244
53438
  */
53245
53439
  function createDiffCommand() {
53246
- const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).addOption(useCdkBootstrapAssetsOption).action(withErrorHandling(diffCommand));
53440
+ const cmd = new Command("diff").description("Show difference between current state and desired state").argument("[stacks...]", "Stack name(s) to diff. Accepts physical CloudFormation names (e.g. 'MyStage-Api') or CDK display paths (e.g. 'MyStage/Api'). Supports wildcards (e.g. 'MyStage/*').").option("--all", "Diff all stacks", false).option("--recursive", "Recurse into each AWS::CloudFormation::Stack row and diff every nested-stack child against its own deployed state (DFS order). Default is non-recursive, matching cdk diff.", false).option("--fail", "Exit with code 1 when any change is detected (matches cdk diff --fail). With --recursive, considers the whole nested-stack tree.", false).option("--json", "Output the diff as JSON (nested tree shape when combined with --recursive)", false).addOption(useCdkBootstrapAssetsOption).addOption(noCfnFallbackOption).action(withErrorHandling(diffCommand));
53247
53441
  [
53248
53442
  ...commonOptions,
53249
53443
  ...appOptions,
@@ -56335,8 +56529,8 @@ async function exportCommand(stackArg, options) {
56335
56529
  const crossRefs = scanCrossStackReferences(allSynthStacks, resolvedStackName);
56336
56530
  if (crossRefs.length > 0) {
56337
56531
  const lines = crossRefs.map((r) => ` ${r.consumerStackName} → ${resolvedStackName}.${r.outputName} at ${r.location}`);
56338
- if (options.strictCrossStack) throw new Error(`Refusing to export: ${crossRefs.length} cross-stack reference(s) to ${resolvedStackName} found in sibling stacks. After migration, those references will break (cdkd's Fn::GetStackOutput reads cdkd state; the migrated stack's outputs live in CFn). Migrate consumers first, or remove the references, or drop --strict-cross-stack to proceed with a warning:\n` + lines.join("\n"));
56339
- logger.warn(`${crossRefs.length} cross-stack reference(s) to '${resolvedStackName}' from sibling stacks. These will break the next time those stacks deploy via cdkd (cdkd's Fn::GetStackOutput resolver reads cdkd state; the migrated stack's outputs are now in CFn). Plan multi-stack migrations from the leaves up.`);
56532
+ if (options.strictCrossStack) throw new Error(`Refusing to export: ${crossRefs.length} cross-stack reference(s) to ${resolvedStackName} found in sibling stacks. After migration, those references depend on the CloudFormation fallback (the migrated stack's outputs live in CFn; cdkd resolves them via DescribeStacks after the cdkd-state miss), and consumers deployed with --no-cfn-fallback break. Migrate consumers first, or remove the references, or drop --strict-cross-stack to proceed with a warning:\n` + lines.join("\n"));
56533
+ logger.warn(`${crossRefs.length} cross-stack reference(s) to '${resolvedStackName}' from sibling stacks. After the migration those consumers resolve this stack's outputs via the CloudFormation fallback (weak reference; the outputs now live in CFn) they break only when deployed with --no-cfn-fallback. Migrate consumers from the leaves up if you rely on cdkd-state-only resolution.`);
56340
56534
  for (const line of lines) logger.warn(line);
56341
56535
  }
56342
56536
  }
@@ -56420,8 +56614,8 @@ async function exportCommand(stackArg, options) {
56420
56614
  const crossRefs = scanCrossStackReferences(allSynthStacks, resolvedStackName);
56421
56615
  if (crossRefs.length > 0) {
56422
56616
  const lines = crossRefs.map((r) => ` ${r.consumerStackName} → ${resolvedStackName}.${r.outputName} at ${r.location}`);
56423
- if (options.strictCrossStack) throw new Error(`Refusing to export: ${crossRefs.length} cross-stack reference(s) to ${resolvedStackName} found in sibling stacks. After migration, those references will break (cdkd's Fn::GetStackOutput reads cdkd state; the migrated stack's outputs live in CFn). Migrate consumers first, or remove the references, or drop --strict-cross-stack to proceed with a warning:\n` + lines.join("\n"));
56424
- logger.warn(`${crossRefs.length} cross-stack reference(s) to '${resolvedStackName}' from sibling stacks. These will break the next time those stacks deploy via cdkd (cdkd's Fn::GetStackOutput resolver reads cdkd state; the migrated stack's outputs are now in CFn). Plan multi-stack migrations from the leaves up.`);
56617
+ if (options.strictCrossStack) throw new Error(`Refusing to export: ${crossRefs.length} cross-stack reference(s) to ${resolvedStackName} found in sibling stacks. After migration, those references depend on the CloudFormation fallback (the migrated stack's outputs live in CFn; cdkd resolves them via DescribeStacks after the cdkd-state miss), and consumers deployed with --no-cfn-fallback break. Migrate consumers first, or remove the references, or drop --strict-cross-stack to proceed with a warning:\n` + lines.join("\n"));
56618
+ logger.warn(`${crossRefs.length} cross-stack reference(s) to '${resolvedStackName}' from sibling stacks. After the migration those consumers resolve this stack's outputs via the CloudFormation fallback (weak reference; the outputs now live in CFn) they break only when deployed with --no-cfn-fallback. Migrate consumers from the leaves up if you rely on cdkd-state-only resolution.`);
56425
56619
  for (const line of lines) logger.warn(line);
56426
56620
  }
56427
56621
  }
@@ -58329,7 +58523,7 @@ async function confirmPrompt$3(prompt) {
58329
58523
  }
58330
58524
  }
58331
58525
  function createExportCommand() {
58332
- const cmd = new Command("export").description("Hand a cdkd-managed stack over to CloudFormation via CFn IMPORT (changeset). AWS resources are unchanged; cdkd state for the stack is deleted on success. Mirror of `cdkd import` (AWS → cdkd) in the reverse direction (cdkd → CFn). Accepts JSON and YAML templates (YAML via a CFn-aware codec that preserves !Ref / !GetAtt / !Sub shorthand). Aborts if any resource is not CFn-importable.").argument("[stack]", "Stack name to export (auto-detected for single-stack apps)").option("--cfn-stack-name <name>", "Name of the destination CloudFormation stack for the root stack. Defaults to the cdkd stack name (or the cdkd2cfnStackName mapping when the name contains '~'). For per-nested-child overrides, use --cfn-child-stack-name.").option("--cfn-child-stack-name <pair...>", "Per-nested-child CFn stack-name override. Repeatable. Format: '<cdkdName>=<cfnName>' (e.g. --cfn-child-stack-name 'MyApp~Database=my-app-db'). The cdkd stack name for a nested child is '<parent>~<childLogicalId>' (v6 state-key form). Without an override the CFn name is derived by replacing '~' with '-'. Only consulted when the exported stack tree contains nested children; ignored for flat stacks.").option("--template <path>", "Path to a pre-rendered CloudFormation template (JSON or YAML — format auto-detected). Skips synth.").option("--stack-region <region>", "Region of the cdkd state record to operate on. Required when the same stack name has state in multiple regions.").option("--dry-run", "Print the import plan without creating a changeset.", false).option("--accept-transient-context", "Allow CLI -c key=value overrides at export time even though they are not persisted to cdk.json / cdk.context.json (default: refuse). When set, the user is responsible for passing the same -c flags to every future cdk deploy.", false).option("--include-non-importable", "Run a 2-phase migration when the stack contains non-importable resources (Custom::*). Phase 1 imports the importable resources; phase 2 CFn-CREATEs the non-importable ones, which re-invokes each Custom Resource's onCreate handler. Make sure onCreate is idempotent before enabling.", false).option("--parameter <key=value...>", "CFn template Parameter override, repeatable. Required when the synthesized template has Parameters without Default values; otherwise overrides the template's default value. Format: --parameter Key=Value.").option("--strict-cross-stack", "Refuse to export when sibling cdkd stacks in the same CDK app reference the exporting stack via Fn::GetStackOutput. Without the flag, cdkd warns but proceeds — the user is expected to migrate the consumer stacks in a follow-up.", false).option("--no-recreate-import-unsupported", "Block instead of auto-handling resource types AWS does NOT support in IMPORT changesets (currently only AWS::ApiGatewayV2::Stage, emitted by CDK HttpApi). Default behavior: cdkd skips these from phase 1, deletes the AWS-side resource between phases, and lets CFn re-CREATE in phase 2 (brief unavailability window). With this flag, the export aborts with a clear error instead.").action(withErrorHandling(exportCommand));
58526
+ const cmd = new Command("export").description("Hand a cdkd-managed stack over to CloudFormation via CFn IMPORT (changeset). AWS resources are unchanged; cdkd state for the stack is deleted on success. Mirror of `cdkd import` (AWS → cdkd) in the reverse direction (cdkd → CFn). Accepts JSON and YAML templates (YAML via a CFn-aware codec that preserves !Ref / !GetAtt / !Sub shorthand). Aborts if any resource is not CFn-importable.").argument("[stack]", "Stack name to export (auto-detected for single-stack apps)").option("--cfn-stack-name <name>", "Name of the destination CloudFormation stack for the root stack. Defaults to the cdkd stack name (or the cdkd2cfnStackName mapping when the name contains '~'). For per-nested-child overrides, use --cfn-child-stack-name.").option("--cfn-child-stack-name <pair...>", "Per-nested-child CFn stack-name override. Repeatable. Format: '<cdkdName>=<cfnName>' (e.g. --cfn-child-stack-name 'MyApp~Database=my-app-db'). The cdkd stack name for a nested child is '<parent>~<childLogicalId>' (v6 state-key form). Without an override the CFn name is derived by replacing '~' with '-'. Only consulted when the exported stack tree contains nested children; ignored for flat stacks.").option("--template <path>", "Path to a pre-rendered CloudFormation template (JSON or YAML — format auto-detected). Skips synth.").option("--stack-region <region>", "Region of the cdkd state record to operate on. Required when the same stack name has state in multiple regions.").option("--dry-run", "Print the import plan without creating a changeset.", false).option("--accept-transient-context", "Allow CLI -c key=value overrides at export time even though they are not persisted to cdk.json / cdk.context.json (default: refuse). When set, the user is responsible for passing the same -c flags to every future cdk deploy.", false).option("--include-non-importable", "Run a 2-phase migration when the stack contains non-importable resources (Custom::*). Phase 1 imports the importable resources; phase 2 CFn-CREATEs the non-importable ones, which re-invokes each Custom Resource's onCreate handler. Make sure onCreate is idempotent before enabling.", false).option("--parameter <key=value...>", "CFn template Parameter override, repeatable. Required when the synthesized template has Parameters without Default values; otherwise overrides the template's default value. Format: --parameter Key=Value.").option("--strict-cross-stack", "Refuse to export when sibling cdkd stacks in the same CDK app reference the exporting stack via Fn::GetStackOutput. Without the flag, cdkd warns and proceeds — the consumers keep resolving via the CloudFormation fallback (weak reference) unless they deploy with --no-cfn-fallback.", false).option("--no-recreate-import-unsupported", "Block instead of auto-handling resource types AWS does NOT support in IMPORT changesets (currently only AWS::ApiGatewayV2::Stage, emitted by CDK HttpApi). Default behavior: cdkd skips these from phase 1, deletes the AWS-side resource between phases, and lets CFn re-CREATE in phase 2 (brief unavailability window). With this flag, the export aborts with a clear error instead.").action(withErrorHandling(exportCommand));
58333
58527
  [
58334
58528
  ...commonOptions,
58335
58529
  ...appOptions,
@@ -72155,7 +72349,7 @@ function createMigrateCommand() {
72155
72349
  */
72156
72350
  function buildProgram() {
72157
72351
  const program = new Command();
72158
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.280.46");
72352
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.0");
72159
72353
  program.hook("preAction", (_thisCommand, actionCommand) => {
72160
72354
  const { profile } = actionCommand.optsWithGlobals();
72161
72355
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;