@go-to-k/cdkd 0.281.3 → 0.281.5

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-DFZo1Twc.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-BNDd1JZs.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-CBIIk31p.js";
4
+ import { t as ASGProvider } from "./asg-provider-DPocF4V6.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";
@@ -5808,7 +5808,9 @@ var S3BucketProvider = class S3BucketProvider {
5808
5808
  continue;
5809
5809
  }
5810
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);
5811
+ const declaresSdkSchedule = config["Schedule"] !== void 0;
5812
+ if (scheduleFrequencyRefusal !== void 0 || declaresSdkSchedule) recordSubstitution(["ScheduleFrequency"], frequency);
5813
+ if (declaresSdkSchedule) sentItem = S3BucketProvider.withoutKey(sentItem ?? config, "Schedule");
5812
5814
  const inventoryConfig = {
5813
5815
  Id: id,
5814
5816
  IsEnabled: config["Enabled"] ?? true,
@@ -6178,6 +6180,85 @@ var S3BucketProvider = class S3BucketProvider {
6178
6180
  };
6179
6181
  }
6180
6182
  /**
6183
+ * The REMOVAL twin of {@link withDeepValue}: a copy of `container` without
6184
+ * `key`, sharing every surviving subtree and never mutating the input (same
6185
+ * reason — the input is an item of the caller's DESIRED bag).
6186
+ *
6187
+ * A substitution answers "the value we sent differs from the declared one";
6188
+ * this answers "the KEY we sent does not exist on the readback at all", which
6189
+ * is the shape-mismatch half of the same phantom-drift class (issue #1686).
6190
+ * Setting the key to `undefined` instead would NOT do: `deepEqual` is
6191
+ * `JSON.stringify`, which drops an `undefined` member — but the state record
6192
+ * is written from this object and a present-but-`undefined` key survives a
6193
+ * `structuredClone`, so the two sides would disagree on key COUNT for any
6194
+ * consumer that walks `Object.keys` (the `unionWalkObjects` drift path does).
6195
+ */
6196
+ static withoutKey(container, key) {
6197
+ if (!(key in container)) return container;
6198
+ const { [key]: _dropped, ...rest } = container;
6199
+ return rest;
6200
+ }
6201
+ /**
6202
+ * The empty-collection placeholder arm of {@link applySubConfigDiffs}: the
6203
+ * Put is SKIPPED, so AWS still holds the previously-applied configuration and
6204
+ * the record must say so (issue #1671).
6205
+ *
6206
+ * Without this the engine recorded `{Rules: []}` / `{CorsRules: []}` — a Put
6207
+ * that never ran, written as though it had. Unlike the malformed-value skips
6208
+ * #1612 settled, this arm is reachable from an ORDINARY template path rather
6209
+ * than only a state replay: a condition-pruned or intrinsic-collapsed
6210
+ * template synthesizes an empty rules array.
6211
+ *
6212
+ * The issue's open question was whether an empty collection is a REMOVAL
6213
+ * intent, which would make the skip itself wrong. **It is not** — measured
6214
+ * against live CloudFormation (us-east-1, 2026-08-12): updating a deployed
6215
+ * bucket to `LifecycleConfiguration: { Rules: [] }` +
6216
+ * `CorsConfiguration: { CorsRules: [] }` drives the stack to
6217
+ * `UPDATE_ROLLBACK_COMPLETE`, and BOTH live configurations survive the
6218
+ * rollback unchanged. So CFn treats the empty collection as an INVALID
6219
+ * template, not as a removal, and the configuration stays. That rules out
6220
+ * the opposite candidate answer: turning this arm into a Delete would both
6221
+ * diverge from CFn and destroy a live configuration the user still wants,
6222
+ * on a template whose only fault is a collapsed array. The registry schema
6223
+ * agrees on the shape (`Rules` / `CorsRules` are REQUIRED, with no
6224
+ * `minItems`, so `[]` parses and only the service refuses it).
6225
+ *
6226
+ * The skip therefore stands and the previous value is what state should
6227
+ * describe — the #1612 UPDATE answer, now with CFn behavior behind it.
6228
+ * Dropping the key instead would be wrong in the other direction: a later
6229
+ * template that genuinely REMOVES the block would derive no removal and the
6230
+ * live configuration would survive forever.
6231
+ *
6232
+ * The skip is also ANNOUNCED rather than silent, because CFn's own answer to
6233
+ * this template is a loud failure — a user whose rules stop being applied
6234
+ * should not have to diff state to find out. It is a warning rather than a
6235
+ * throw so the `readCurrentState` round-trip this arm exists to absorb
6236
+ * (`cdkd drift --revert` feeds an always-emitted empty-rules block back
6237
+ * through `update()`) keeps working.
6238
+ *
6239
+ * `collectionPath` is the WHOLE dotted path as ONE literal
6240
+ * (`'CorsConfiguration.CorsRules'`), never the bare terminal segment, and
6241
+ * that is load-bearing rather than stylistic. `gen-nested-key-coverage`'s key
6242
+ * pass takes the provider's bare string literals as evidence of explicit
6243
+ * per-key handling and keeps the STRICT literal set (only the shape pass
6244
+ * dot-expands), so the two spellings are not interchangeable here. Measured
6245
+ * against the shipped critic: a bare `'CorsRules'` literal anywhere in this
6246
+ * file turns the reviewed `AWS::S3::Bucket#CorsConfiguration.CorsRules`
6247
+ * allow-list entry STALE, while the joined path leaves the audit unchanged.
6248
+ * That entry's whole rationale is that the key is delivered by TYPED PROPERTY
6249
+ * ACCESS in `applyCorsConfiguration` and that its only literal mention is
6250
+ * `readCors`, excluded as a reverse map — so satisfying the critic by
6251
+ * deleting the entry would leave the key classified as handled on nothing but
6252
+ * a warning string, and a later real drop of `CorsRules` would pass in
6253
+ * silence. That is the decoy-literal shape `.claude/rules/providers.md`
6254
+ * forbids.
6255
+ */
6256
+ emptyCollectionSkip(key, collectionPath, previousProperties, retainPrevious) {
6257
+ const hadPrevious = previousProperties[key] !== void 0;
6258
+ this.logger.warn(`AWS::S3::Bucket ${collectionPath} declares no rules, which CloudFormation rejects outright (the collection is a required property, and an empty one is not a removal intent). Leaving the live ${key} untouched and ` + (hadPrevious ? `recording the previously-applied value` : `recording no ${key} (nothing was applied before)`) + `; remove the whole ${key} property to delete the configuration.`);
6259
+ retainPrevious(key);
6260
+ }
6261
+ /**
6181
6262
  * Diff CFn-shape sub-config values between previous and new state.
6182
6263
  *
6183
6264
  * Four transitions:
@@ -6311,14 +6392,20 @@ var S3BucketProvider = class S3BucketProvider {
6311
6392
  this.logger.debug(`Deleted bucket encryption on bucket ${bucketName}`);
6312
6393
  });
6313
6394
  await this.diffSubConfig(bucketName, previousProperties["LifecycleConfiguration"], properties["LifecycleConfiguration"], async (cfg) => {
6314
- if (!cfg.Rules || !Array.isArray(cfg.Rules) || cfg.Rules.length === 0) return;
6395
+ if (!cfg.Rules || !Array.isArray(cfg.Rules) || cfg.Rules.length === 0) {
6396
+ this.emptyCollectionSkip("LifecycleConfiguration", "LifecycleConfiguration.Rules", previousProperties, retainPrevious);
6397
+ return;
6398
+ }
6315
6399
  if (!await this.applyLifecycleConfiguration(bucketName, cfg, (m) => this.logger.warn(m))) retainPrevious("LifecycleConfiguration");
6316
6400
  }, async () => {
6317
6401
  await this.s3Client.send(new DeleteBucketLifecycleCommand({ Bucket: bucketName }));
6318
6402
  this.logger.debug(`Deleted lifecycle configuration on bucket ${bucketName}`);
6319
6403
  });
6320
6404
  await this.diffSubConfig(bucketName, previousProperties["CorsConfiguration"], properties["CorsConfiguration"], async (cfg) => {
6321
- if (!cfg.CorsRules || !Array.isArray(cfg.CorsRules) || cfg.CorsRules.length === 0) return;
6405
+ if (!cfg.CorsRules || !Array.isArray(cfg.CorsRules) || cfg.CorsRules.length === 0) {
6406
+ this.emptyCollectionSkip("CorsConfiguration", "CorsConfiguration.CorsRules", previousProperties, retainPrevious);
6407
+ return;
6408
+ }
6322
6409
  await this.applyCorsConfiguration(bucketName, cfg);
6323
6410
  }, async () => {
6324
6411
  await this.s3Client.send(new DeleteBucketCorsCommand({ Bucket: bucketName }));
@@ -11597,6 +11684,123 @@ var LambdaLayerVersionProvider = class {
11597
11684
  }
11598
11685
  };
11599
11686
 
11687
+ //#endregion
11688
+ //#region src/provisioning/composite-id.ts
11689
+ /**
11690
+ * Refuse a composite physicalId whose segments would make it ambiguous
11691
+ * (issue [#1672](https://github.com/go-to-k/cdkd/issues/1672)).
11692
+ *
11693
+ * ## Why a composite id exists at all
11694
+ *
11695
+ * `ResourceProvider` passes a SINGLE `physicalId: string` as a resource's
11696
+ * identity, while CloudFormation keeps the physical id and its containing
11697
+ * scope as separate values. So every provider whose type needs more than one
11698
+ * value to address the resource PACKS them into one string joined by
11699
+ * {@link COMPOSITE_ID_SEPARATOR} — `<databaseName>|<tableName>` for
11700
+ * `AWS::Glue::Table`, `<apiId>|<typeName>|<fieldName>` for
11701
+ * `AWS::AppSync::Resolver`, and so on.
11702
+ *
11703
+ * ## Why it has to be guarded
11704
+ *
11705
+ * The separator is NOT escaped, so a segment that itself contains a `|`
11706
+ * produces an id with the wrong arity. Every decode site splits the stored id
11707
+ * back apart and the extra segment shifts everything — for a Glue table named
11708
+ * `a|b` in database `mydb` the recorded id `mydb|a|b` decodes to database
11709
+ * `mydb`, table `a`. Both halves are non-empty, so every existing
11710
+ * "is this id well-formed?" guard passes and the deploy SUCCEEDS. What breaks
11711
+ * is everything afterwards: `cdkd destroy` deletes the WRONG resource if one
11712
+ * exists under the decoded pair (or warn-and-skips, reporting success while
11713
+ * the real resource stays alive and billing), and `cdkd drift` reads back
11714
+ * `undefined` forever.
11715
+ *
11716
+ * AWS itself accepts such a name — verified live us-east-1 2026-08-12,
11717
+ * `glue:CreateTable` with `TableInput.Name: 'a|b'` succeeds — and
11718
+ * CloudFormation manages the resource fine. The limitation is cdkd's own, so
11719
+ * the honest answer is to REFUSE at deploy time with a message that says so,
11720
+ * rather than to record an id that silently names something else. Escaping the
11721
+ * separator, or dropping the packing where the decode sites already receive
11722
+ * the properties bag, are the two real fixes and both remain open on #1672;
11723
+ * this guard is what closes the data-loss path in the meantime.
11724
+ *
11725
+ * ## Two entry points, mirroring `config-shape.ts`
11726
+ *
11727
+ * {@link packCompositeId} is the ACTION (throw, or warn-and-pack under the
11728
+ * replay downgrade) and {@link compositeIdSeparatorRefusal} is the bare
11729
+ * PREDICATE, for a caller whose response is neither — the `import()` paths
11730
+ * warn and return `null` (`skipped-not-found`), which is the idiom those
11731
+ * methods already use for every other unusable id. Both share one message
11732
+ * builder so the two can never drift.
11733
+ *
11734
+ * ## Where to place the call
11735
+ *
11736
+ * Prefer computing the id BEFORE the create call rather than after it. The
11737
+ * refusal is a pre-flight check, and a throw placed after the AWS mutation has
11738
+ * already committed would leave an orphan resource behind with no state
11739
+ * record. Only where a segment is derived from the create RESPONSE (an
11740
+ * AWS-generated id) does the call have to sit after it — and those segments
11741
+ * are structurally incapable of carrying a `|` anyway. A post-call site must
11742
+ * also sit OUTSIDE the `try` whose `catch` re-wraps as
11743
+ * `ProvisioningError('Failed to create …')`, or a refusal would be
11744
+ * mis-reported as an AWS creation failure.
11745
+ *
11746
+ * ## What this does NOT cover
11747
+ *
11748
+ * The claim is "every composite packer this change reaches", not "every `|`
11749
+ * join in the tree". Two deliberate exclusions, recorded so the scope is not
11750
+ * read as wider than it is:
11751
+ *
11752
+ * - **`route53-provider.ts`'s three packers** (`AWS::Route53::RecordSet` and
11753
+ * its siblings) are NOT guarded — the file was owned by a parallel lane when
11754
+ * this shipped. The exposure there is bounded rather than absent:
11755
+ * `parseRecordSetCompositeId` requires EXACTLY three parts, so a four-part
11756
+ * id is REJECTED rather than mis-decoded into a different record. That is
11757
+ * the loud failure mode, not the silent-wrong-resource one this helper
11758
+ * exists for — but the deploy still records an id nothing can decode, so the
11759
+ * type belongs here whenever that lane lands. Tracked in issue
11760
+ * [#1711](https://github.com/go-to-k/cdkd/issues/1711).
11761
+ * - **`intrinsic-function-resolver.ts`'s `|` joins** (the WAFv2 and
11762
+ * `AWS::Events::Rule` arms) are out of scope by KIND, not by ownership: they
11763
+ * build a `Ref` VALUE for the template resolver, never a physicalId cdkd
11764
+ * records and later splits. Nothing decodes them, so an ambiguous join has
11765
+ * no decode site to go wrong at.
11766
+ */
11767
+ /** The character every composite physicalId in this codebase joins segments with. */
11768
+ const COMPOSITE_ID_SEPARATOR = "|";
11769
+ /** The issue tracking the real fix (escaping, or not packing at all). */
11770
+ const COMPOSITE_ID_ISSUE_URL = "https://github.com/go-to-k/cdkd/issues/1672";
11771
+ /** Render `<a>|<b>|<c>` from the segment names. */
11772
+ function idShape(segments) {
11773
+ return segments.map((segment) => `<${segment.name}>`).join("|");
11774
+ }
11775
+ /**
11776
+ * The refusal sentence for a composite id whose segments carry the separator,
11777
+ * or `undefined` when the id is unambiguous.
11778
+ *
11779
+ * Every offending segment is named, not just the first: a template that
11780
+ * mis-names one value frequently mis-names its sibling too, and reporting them
11781
+ * one deploy at a time is the shape of remedy nobody finishes.
11782
+ */
11783
+ function compositeIdSeparatorRefusal(resourceType, logicalId, segments) {
11784
+ const offending = segments.filter((segment) => String(segment.value).includes("|"));
11785
+ if (offending.length === 0) return void 0;
11786
+ return `${resourceType} ${logicalId}: ${offending.map((segment) => `${segment.name} '${String(segment.value)}'`).join(" and ")} contains '${"|"}', which cdkd uses as the separator in this type's physical id (${idShape(segments)}). Recording it would produce an id that decodes back to a DIFFERENT resource, so a later cdkd destroy / drift / update would target the wrong one — or silently skip it while the real resource stays alive. AWS accepts the name and CloudFormation manages such a resource fine; this is a cdkd limitation tracked in ${COMPOSITE_ID_ISSUE_URL}. Rename the resource so no segment contains '${"|"}', or manage it outside cdkd.`;
11787
+ }
11788
+ /**
11789
+ * Join the segments into a composite physicalId, REFUSING one that would be
11790
+ * ambiguous.
11791
+ *
11792
+ * @throws ProvisioningError when a segment contains {@link COMPOSITE_ID_SEPARATOR},
11793
+ * unless `options.onRefusal` is supplied — under which the message is handed
11794
+ * to the callback and the ambiguous id is returned, keeping the pre-guard
11795
+ * behavior.
11796
+ */
11797
+ function packCompositeId(resourceType, logicalId, segments, options) {
11798
+ const refusal = compositeIdSeparatorRefusal(resourceType, logicalId, segments);
11799
+ 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.`);
11800
+ else throw new ProvisioningError(refusal, resourceType, logicalId);
11801
+ return segments.map((segment) => String(segment.value)).join("|");
11802
+ }
11803
+
11600
11804
  //#endregion
11601
11805
  //#region src/provisioning/providers/lambda-event-invoke-config-provider.ts
11602
11806
  /**
@@ -11640,8 +11844,14 @@ var LambdaEventInvokeConfigProvider = class {
11640
11844
  /**
11641
11845
  * Compose the Cloud-Control-compatible compound physical id.
11642
11846
  */
11643
- buildPhysicalId(functionName, qualifier) {
11644
- return `${functionName}|${qualifier}`;
11847
+ buildPhysicalId(logicalId, functionName, qualifier, options) {
11848
+ return packCompositeId("AWS::Lambda::EventInvokeConfig", logicalId, [{
11849
+ name: "functionName",
11850
+ value: functionName
11851
+ }, {
11852
+ name: "qualifier",
11853
+ value: qualifier
11854
+ }], options);
11645
11855
  }
11646
11856
  /**
11647
11857
  * Split a `<FunctionName>|<Qualifier>` physical id back into its parts.
@@ -11699,9 +11909,9 @@ var LambdaEventInvokeConfigProvider = class {
11699
11909
  coerceNumber: true,
11700
11910
  ...replayWarn(this.logger, context)
11701
11911
  });
11912
+ const physicalId = this.buildPhysicalId(logicalId, functionName, qualifier, context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
11702
11913
  try {
11703
11914
  await this.lambdaClient.send(new PutFunctionEventInvokeConfigCommand(this.buildPutInput(properties)));
11704
- const physicalId = this.buildPhysicalId(functionName, qualifier);
11705
11915
  this.logger.debug(`Successfully created Lambda EventInvokeConfig ${logicalId}: ${physicalId}`);
11706
11916
  return {
11707
11917
  physicalId,
@@ -18860,7 +19070,7 @@ var EC2Provider = class {
18860
19070
  case "AWS::EC2::Subnet": return this.createSubnet(logicalId, resourceType, properties);
18861
19071
  case "AWS::EC2::InternetGateway": return this.createInternetGateway(logicalId, resourceType, properties);
18862
19072
  case "AWS::EC2::EIP": return this.createEip(logicalId, resourceType, properties, context);
18863
- case "AWS::EC2::VPCGatewayAttachment": return this.createVpcGatewayAttachment(logicalId, resourceType, properties);
19073
+ case "AWS::EC2::VPCGatewayAttachment": return this.createVpcGatewayAttachment(logicalId, resourceType, properties, context);
18864
19074
  case "AWS::EC2::NatGateway": return this.createNatGateway(logicalId, resourceType, properties);
18865
19075
  case "AWS::EC2::RouteTable": return this.createRouteTable(logicalId, resourceType, properties);
18866
19076
  case "AWS::EC2::Route": return this.createRoute(logicalId, resourceType, properties, context?.replayingState === true ? (message) => this.logger.warn(message) : void 0);
@@ -18869,7 +19079,7 @@ var EC2Provider = class {
18869
19079
  case "AWS::EC2::SecurityGroupIngress": return this.createSecurityGroupIngress(logicalId, resourceType, properties, context?.replayingState === true ? (message) => this.logger.warn(message) : void 0);
18870
19080
  case "AWS::EC2::Instance": return this.createInstance(logicalId, resourceType, properties, context);
18871
19081
  case "AWS::EC2::NetworkAcl": return this.createNetworkAcl(logicalId, resourceType, properties);
18872
- case "AWS::EC2::NetworkAclEntry": return this.createNetworkAclEntry(logicalId, resourceType, properties);
19082
+ case "AWS::EC2::NetworkAclEntry": return this.createNetworkAclEntry(logicalId, resourceType, properties, context);
18873
19083
  case "AWS::EC2::SubnetNetworkAclAssociation": return this.createSubnetNetworkAclAssociation(logicalId, resourceType, properties);
18874
19084
  default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
18875
19085
  }
@@ -19301,8 +19511,24 @@ var EC2Provider = class {
19301
19511
  throw new ProvisioningError(`Failed to delete InternetGateway ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
19302
19512
  }
19303
19513
  }
19304
- eipPhysicalId(publicIp, allocationId) {
19305
- return `${publicIp}|${allocationId}`;
19514
+ /**
19515
+ * Both segments are AWS-generated (a dotted-quad address and an
19516
+ * `eipalloc-...` id), so neither can carry cdkd's `|` separator and the
19517
+ * refusal below is a fence rather than a live guard (issue #1672). It is
19518
+ * applied uniformly with the rest of the composite-packing sites so a future
19519
+ * id-shape change cannot quietly reintroduce the ambiguity; for the same
19520
+ * reason it is safe on BOTH callers — `createEip` (post-`AllocateAddress`)
19521
+ * and `import()` (post-`DescribeAddresses`) — even though a throw on either
19522
+ * would arrive after the AWS call.
19523
+ */
19524
+ eipPhysicalId(logicalId, publicIp, allocationId, options) {
19525
+ return packCompositeId("AWS::EC2::EIP", logicalId, [{
19526
+ name: "publicIp",
19527
+ value: publicIp
19528
+ }, {
19529
+ name: "allocationId",
19530
+ value: allocationId
19531
+ }], options);
19306
19532
  }
19307
19533
  parseEipPhysicalId(physicalId) {
19308
19534
  if (physicalId.includes("|")) {
@@ -19317,14 +19543,16 @@ var EC2Provider = class {
19317
19543
  }
19318
19544
  async createEip(logicalId, resourceType, properties, context) {
19319
19545
  this.logger.debug(`Creating EIP ${logicalId}`);
19546
+ let allocationId;
19547
+ let publicIp;
19320
19548
  try {
19321
19549
  const response = await this.ec2Client.send(new AllocateAddressCommand({
19322
19550
  Domain: requireConfigString(properties["Domain"], "vpc", "AWS::EC2::EIP Domain", replayWarn(this.logger, context)),
19323
19551
  NetworkBorderGroup: properties["NetworkBorderGroup"],
19324
19552
  PublicIpv4Pool: properties["PublicIpv4Pool"]
19325
19553
  }));
19326
- const allocationId = response.AllocationId;
19327
- const publicIp = response.PublicIp;
19554
+ allocationId = response.AllocationId;
19555
+ publicIp = response.PublicIp;
19328
19556
  await this.applyTags(allocationId, properties, logicalId);
19329
19557
  const instanceId = properties["InstanceId"];
19330
19558
  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}`);
@@ -19336,17 +19564,17 @@ var EC2Provider = class {
19336
19564
  this.logger.debug(`Associated EIP ${logicalId} (${allocationId}) to ${instanceId}`);
19337
19565
  }
19338
19566
  this.logger.debug(`Successfully created EIP ${logicalId}: ${allocationId} (${publicIp})`);
19339
- return {
19340
- physicalId: this.eipPhysicalId(publicIp, allocationId),
19341
- attributes: {
19342
- AllocationId: allocationId,
19343
- PublicIp: publicIp
19344
- }
19345
- };
19346
19567
  } catch (error) {
19347
19568
  const cause = error instanceof Error ? error : void 0;
19348
19569
  throw new ProvisioningError(`Failed to create EIP ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
19349
19570
  }
19571
+ return {
19572
+ physicalId: this.eipPhysicalId(logicalId, publicIp, allocationId, context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0),
19573
+ attributes: {
19574
+ AllocationId: allocationId,
19575
+ PublicIp: publicIp
19576
+ }
19577
+ };
19350
19578
  }
19351
19579
  async updateEip(logicalId, physicalId, properties, previousProperties) {
19352
19580
  this.logger.debug(`Updating EIP ${logicalId}: ${physicalId}`);
@@ -19412,17 +19640,23 @@ var EC2Provider = class {
19412
19640
  default: return;
19413
19641
  }
19414
19642
  }
19415
- async createVpcGatewayAttachment(logicalId, resourceType, properties) {
19643
+ async createVpcGatewayAttachment(logicalId, resourceType, properties, context) {
19416
19644
  this.logger.debug(`Creating VPCGatewayAttachment ${logicalId}`);
19417
19645
  const vpcId = properties["VpcId"];
19418
19646
  const internetGatewayId = properties["InternetGatewayId"];
19419
19647
  if (!vpcId || !internetGatewayId) throw new ProvisioningError(`VpcId and InternetGatewayId are required for VPCGatewayAttachment ${logicalId}`, resourceType, logicalId);
19648
+ const physicalId = packCompositeId(resourceType, logicalId, [{
19649
+ name: "internetGatewayId",
19650
+ value: internetGatewayId
19651
+ }, {
19652
+ name: "vpcId",
19653
+ value: vpcId
19654
+ }], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
19420
19655
  try {
19421
19656
  await this.ec2Client.send(new AttachInternetGatewayCommand({
19422
19657
  VpcId: vpcId,
19423
19658
  InternetGatewayId: internetGatewayId
19424
19659
  }));
19425
- const physicalId = `${internetGatewayId}|${vpcId}`;
19426
19660
  this.logger.debug(`Successfully created VPCGatewayAttachment ${logicalId}: ${physicalId}`);
19427
19661
  return {
19428
19662
  physicalId,
@@ -19574,6 +19808,13 @@ var EC2Provider = class {
19574
19808
  onMultipleDestinations(`${message} Continuing with ${usedKey} and ignoring the rest.`);
19575
19809
  } else throw new ProvisioningError(message, resourceType, logicalId);
19576
19810
  }
19811
+ const physicalId = packCompositeId(resourceType, logicalId, [{
19812
+ name: "routeTableId",
19813
+ value: routeTableId
19814
+ }, {
19815
+ name: "destination",
19816
+ value: destination
19817
+ }], onMultipleDestinations ? { onRefusal: onMultipleDestinations } : void 0);
19577
19818
  try {
19578
19819
  await this.ec2Client.send(new CreateRouteCommand({
19579
19820
  RouteTableId: routeTableId,
@@ -19590,7 +19831,6 @@ var EC2Provider = class {
19590
19831
  VpcEndpointId: properties["VpcEndpointId"] ?? void 0,
19591
19832
  VpcPeeringConnectionId: properties["VpcPeeringConnectionId"] ?? void 0
19592
19833
  }));
19593
- const physicalId = `${routeTableId}|${destination}`;
19594
19834
  this.logger.debug(`Successfully created Route ${logicalId}: ${physicalId}`);
19595
19835
  return {
19596
19836
  physicalId,
@@ -19901,6 +20141,24 @@ var EC2Provider = class {
19901
20141
  const effectiveProperties = narrowed === properties ? void 0 : narrowed;
19902
20142
  const fromPort = properties["FromPort"];
19903
20143
  const toPort = properties["ToPort"];
20144
+ const physicalId = packCompositeId(resourceType, logicalId, [
20145
+ {
20146
+ name: "groupId",
20147
+ value: groupId
20148
+ },
20149
+ {
20150
+ name: "ipProtocol",
20151
+ value: ipProtocol
20152
+ },
20153
+ {
20154
+ name: "fromPort",
20155
+ value: fromPort ?? "-1"
20156
+ },
20157
+ {
20158
+ name: "toPort",
20159
+ value: toPort ?? "-1"
20160
+ }
20161
+ ], onUnusableProtocol ? { onRefusal: onUnusableProtocol } : void 0);
19904
20162
  try {
19905
20163
  await this.ec2Client.send(new AuthorizeSecurityGroupIngressCommand({
19906
20164
  GroupId: groupId,
@@ -19909,7 +20167,6 @@ var EC2Provider = class {
19909
20167
  IpProtocol: ipProtocol
19910
20168
  }]
19911
20169
  }));
19912
- const physicalId = `${groupId}|${ipProtocol}|${fromPort ?? "-1"}|${toPort ?? "-1"}`;
19913
20170
  this.logger.debug(`Successfully created SecurityGroupIngress ${logicalId}: ${physicalId}`);
19914
20171
  return {
19915
20172
  physicalId,
@@ -19918,7 +20175,6 @@ var EC2Provider = class {
19918
20175
  };
19919
20176
  } catch (error) {
19920
20177
  if (error instanceof Error && error.message.includes("already exists")) {
19921
- const physicalId = `${groupId}|${ipProtocol}|${fromPort ?? "-1"}|${toPort ?? "-1"}`;
19922
20178
  this.logger.debug(`SecurityGroupIngress ${logicalId} already exists, treating as success`);
19923
20179
  return {
19924
20180
  physicalId,
@@ -20586,7 +20842,7 @@ var EC2Provider = class {
20586
20842
  throw new ProvisioningError(`Failed to delete NetworkAcl ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
20587
20843
  }
20588
20844
  }
20589
- async createNetworkAclEntry(logicalId, resourceType, properties) {
20845
+ async createNetworkAclEntry(logicalId, resourceType, properties, context) {
20590
20846
  this.logger.debug(`Creating NetworkAclEntry ${logicalId}`);
20591
20847
  const networkAclId = properties["NetworkAclId"];
20592
20848
  const ruleNumber = properties["RuleNumber"];
@@ -20594,6 +20850,20 @@ var EC2Provider = class {
20594
20850
  const ruleAction = properties["RuleAction"];
20595
20851
  const egress = properties["Egress"] ?? false;
20596
20852
  if (!networkAclId || ruleNumber === void 0 || protocol === void 0 || !ruleAction) throw new ProvisioningError(`NetworkAclId, RuleNumber, Protocol, and RuleAction are required for NetworkAclEntry ${logicalId}`, resourceType, logicalId);
20853
+ const physicalId = packCompositeId(resourceType, logicalId, [
20854
+ {
20855
+ name: "networkAclId",
20856
+ value: networkAclId
20857
+ },
20858
+ {
20859
+ name: "ruleNumber",
20860
+ value: ruleNumber
20861
+ },
20862
+ {
20863
+ name: "egress",
20864
+ value: egress
20865
+ }
20866
+ ], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
20597
20867
  try {
20598
20868
  const cidrBlock = properties["CidrBlock"];
20599
20869
  const ipv6CidrBlock = properties["Ipv6CidrBlock"];
@@ -20616,7 +20886,6 @@ var EC2Provider = class {
20616
20886
  Type: icmpTypeCode["Type"]
20617
20887
  } : void 0
20618
20888
  }));
20619
- const physicalId = `${networkAclId}|${ruleNumber}|${egress}`;
20620
20889
  this.logger.debug(`Successfully created NetworkAclEntry ${logicalId}: ${physicalId}`);
20621
20890
  return {
20622
20891
  physicalId,
@@ -20875,7 +21144,7 @@ var EC2Provider = class {
20875
21144
  * physical-name property instead.
20876
21145
  */
20877
21146
  async import(input) {
20878
- if (input.knownPhysicalId) return this.verifyExplicit(input.resourceType, input.knownPhysicalId);
21147
+ if (input.knownPhysicalId) return this.verifyExplicit(input.logicalId, input.resourceType, input.knownPhysicalId);
20879
21148
  return null;
20880
21149
  }
20881
21150
  /**
@@ -21436,7 +21705,7 @@ var EC2Provider = class {
21436
21705
  }
21437
21706
  }
21438
21707
  }
21439
- async verifyExplicit(resourceType, physicalId) {
21708
+ async verifyExplicit(logicalId, resourceType, physicalId) {
21440
21709
  try {
21441
21710
  switch (resourceType) {
21442
21711
  case "AWS::EC2::VPC": return (await this.ec2Client.send(new DescribeVpcsCommand({ VpcIds: [physicalId] }))).Vpcs?.[0] ? {
@@ -21460,8 +21729,19 @@ var EC2Provider = class {
21460
21729
  const filter = allocationId ? { AllocationIds: [allocationId] } : { PublicIps: publicIp ? [publicIp] : [] };
21461
21730
  const addr = (await this.ec2Client.send(new DescribeAddressesCommand(filter))).Addresses?.[0];
21462
21731
  if (!addr?.AllocationId || !addr.PublicIp) return null;
21732
+ const eipRefusal = compositeIdSeparatorRefusal("AWS::EC2::EIP", logicalId, [{
21733
+ name: "publicIp",
21734
+ value: addr.PublicIp
21735
+ }, {
21736
+ name: "allocationId",
21737
+ value: addr.AllocationId
21738
+ }]);
21739
+ if (eipRefusal !== void 0) {
21740
+ this.logger.warn(`${eipRefusal} Skipping import.`);
21741
+ return null;
21742
+ }
21463
21743
  return {
21464
- physicalId: this.eipPhysicalId(addr.PublicIp, addr.AllocationId),
21744
+ physicalId: this.eipPhysicalId(logicalId, addr.PublicIp, addr.AllocationId),
21465
21745
  attributes: {
21466
21746
  AllocationId: addr.AllocationId,
21467
21747
  PublicIp: addr.PublicIp
@@ -22468,6 +22748,20 @@ var ApiGatewayProvider = class ApiGatewayProvider {
22468
22748
  const requestValidatorId = properties["RequestValidatorId"];
22469
22749
  const authorizationScopes = properties["AuthorizationScopes"];
22470
22750
  if (!restApiId || !resourceId || !httpMethod) throw new ProvisioningError(`RestApiId, ResourceId, and HttpMethod are required for API Gateway Method ${logicalId}`, resourceType, logicalId);
22751
+ const physicalId = packCompositeId(resourceType, logicalId, [
22752
+ {
22753
+ name: "restApiId",
22754
+ value: restApiId
22755
+ },
22756
+ {
22757
+ name: "resourceId",
22758
+ value: resourceId
22759
+ },
22760
+ {
22761
+ name: "httpMethod",
22762
+ value: httpMethod
22763
+ }
22764
+ ], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
22471
22765
  try {
22472
22766
  await this.apiGatewayClient.send(new PutMethodCommand({
22473
22767
  restApiId,
@@ -22545,7 +22839,6 @@ var ApiGatewayProvider = class ApiGatewayProvider {
22545
22839
  }
22546
22840
  throw innerError;
22547
22841
  }
22548
- const physicalId = `${restApiId}|${resourceId}|${httpMethod}`;
22549
22842
  this.logger.debug(`Successfully created API Gateway Method ${logicalId}: ${physicalId}`);
22550
22843
  return {
22551
22844
  physicalId,
@@ -36511,9 +36804,9 @@ var AppSyncProvider = class {
36511
36804
  switch (resourceType) {
36512
36805
  case "AWS::AppSync::GraphQLApi": return this.createGraphQLApi(logicalId, resourceType, properties, context);
36513
36806
  case "AWS::AppSync::GraphQLSchema": return this.createGraphQLSchema(logicalId, resourceType, properties);
36514
- case "AWS::AppSync::DataSource": return this.createDataSource(logicalId, resourceType, properties);
36515
- case "AWS::AppSync::Resolver": return this.createResolver(logicalId, resourceType, properties);
36516
- case "AWS::AppSync::ApiKey": return this.createApiKey(logicalId, resourceType, properties);
36807
+ case "AWS::AppSync::DataSource": return this.createDataSource(logicalId, resourceType, properties, context);
36808
+ case "AWS::AppSync::Resolver": return this.createResolver(logicalId, resourceType, properties, context);
36809
+ case "AWS::AppSync::ApiKey": return this.createApiKey(logicalId, resourceType, properties, context);
36517
36810
  default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
36518
36811
  }
36519
36812
  }
@@ -37427,12 +37720,19 @@ var AppSyncProvider = class {
37427
37720
  throw new ProvisioningError(`Failed to create GraphQL Schema ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
37428
37721
  }
37429
37722
  }
37430
- async createDataSource(logicalId, resourceType, properties) {
37723
+ async createDataSource(logicalId, resourceType, properties, context) {
37431
37724
  this.logger.debug(`Creating DataSource ${logicalId}`);
37432
37725
  const apiId = properties["ApiId"];
37433
37726
  const name = properties["Name"];
37434
37727
  const type = properties["Type"];
37435
37728
  if (!apiId || !name || !type) throw new ProvisioningError(`ApiId, Name, and Type are required for DataSource ${logicalId}`, resourceType, logicalId);
37729
+ const physicalId = packCompositeId(resourceType, logicalId, [{
37730
+ name: "apiId",
37731
+ value: apiId
37732
+ }, {
37733
+ name: "name",
37734
+ value: name
37735
+ }], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
37436
37736
  try {
37437
37737
  const input = {
37438
37738
  apiId,
@@ -37441,7 +37741,6 @@ var AppSyncProvider = class {
37441
37741
  };
37442
37742
  this.applyDataSourceConfig(input, resourceType, logicalId, properties);
37443
37743
  await this.getClient().send(new CreateDataSourceCommand(input));
37444
- const physicalId = `${apiId}|${name}`;
37445
37744
  this.logger.debug(`Successfully created DataSource ${logicalId}: ${physicalId}`);
37446
37745
  return {
37447
37746
  physicalId,
@@ -37478,12 +37777,26 @@ var AppSyncProvider = class {
37478
37777
  throw new ProvisioningError(`Failed to delete DataSource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
37479
37778
  }
37480
37779
  }
37481
- async createResolver(logicalId, resourceType, properties) {
37780
+ async createResolver(logicalId, resourceType, properties, context) {
37482
37781
  this.logger.debug(`Creating Resolver ${logicalId}`);
37483
37782
  const apiId = properties["ApiId"];
37484
37783
  const typeName = properties["TypeName"];
37485
37784
  const fieldName = properties["FieldName"];
37486
37785
  if (!apiId || !typeName || !fieldName) throw new ProvisioningError(`ApiId, TypeName, and FieldName are required for Resolver ${logicalId}`, resourceType, logicalId);
37786
+ const physicalId = packCompositeId(resourceType, logicalId, [
37787
+ {
37788
+ name: "apiId",
37789
+ value: apiId
37790
+ },
37791
+ {
37792
+ name: "typeName",
37793
+ value: typeName
37794
+ },
37795
+ {
37796
+ name: "fieldName",
37797
+ value: fieldName
37798
+ }
37799
+ ], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
37487
37800
  try {
37488
37801
  const input = {
37489
37802
  apiId,
@@ -37492,7 +37805,6 @@ var AppSyncProvider = class {
37492
37805
  };
37493
37806
  await this.applyResolverConfig(input, resourceType, logicalId, properties);
37494
37807
  await this.getClient().send(new CreateResolverCommand(input));
37495
- const physicalId = `${apiId}|${typeName}|${fieldName}`;
37496
37808
  this.logger.debug(`Successfully created Resolver ${logicalId}: ${physicalId}`);
37497
37809
  return {
37498
37810
  physicalId,
@@ -37528,28 +37840,34 @@ var AppSyncProvider = class {
37528
37840
  throw new ProvisioningError(`Failed to delete Resolver ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
37529
37841
  }
37530
37842
  }
37531
- async createApiKey(logicalId, resourceType, properties) {
37843
+ async createApiKey(logicalId, resourceType, properties, context) {
37532
37844
  this.logger.debug(`Creating ApiKey ${logicalId}`);
37533
37845
  const apiId = properties["ApiId"];
37534
37846
  if (!apiId) throw new ProvisioningError(`ApiId is required for ApiKey ${logicalId}`, resourceType, logicalId);
37847
+ let apiKeyId;
37535
37848
  try {
37536
37849
  const input = { apiId };
37537
37850
  if (properties["Description"]) input.description = properties["Description"];
37538
37851
  if (properties["Expires"]) input.expires = properties["Expires"];
37539
- const response = await this.getClient().send(new CreateApiKeyCommand(input));
37540
- const apiKeyId = response.apiKey.id;
37852
+ apiKeyId = (await this.getClient().send(new CreateApiKeyCommand(input))).apiKey.id;
37541
37853
  this.logger.debug(`Successfully created ApiKey ${logicalId}: ${apiKeyId}`);
37542
- return {
37543
- physicalId: `${apiId}|${apiKeyId}`,
37544
- attributes: {
37545
- ApiKey: response.apiKey.id,
37546
- Arn: `arn:aws:appsync:*:*:apis/${apiId}/apikeys/${apiKeyId}`
37547
- }
37548
- };
37549
37854
  } catch (error) {
37550
37855
  const cause = error instanceof Error ? error : void 0;
37551
37856
  throw new ProvisioningError(`Failed to create ApiKey ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
37552
37857
  }
37858
+ return {
37859
+ physicalId: packCompositeId(resourceType, logicalId, [{
37860
+ name: "apiId",
37861
+ value: apiId
37862
+ }, {
37863
+ name: "apiKeyId",
37864
+ value: apiKeyId
37865
+ }], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0),
37866
+ attributes: {
37867
+ ApiKey: apiKeyId,
37868
+ Arn: `arn:aws:appsync:*:*:apis/${apiId}/apikeys/${apiKeyId}`
37869
+ }
37870
+ };
37553
37871
  }
37554
37872
  async deleteApiKey(logicalId, physicalId, resourceType, context) {
37555
37873
  this.logger.debug(`Deleting ApiKey ${logicalId}: ${physicalId}`);
@@ -38209,6 +38527,13 @@ var GlueProvider = class {
38209
38527
  replayingState: context?.replayingState === true
38210
38528
  });
38211
38529
  const openTableFormatInput = properties["OpenTableFormatInput"];
38530
+ const physicalId = packCompositeId(resourceType, logicalId, [{
38531
+ name: "databaseName",
38532
+ value: databaseName
38533
+ }, {
38534
+ name: "tableName",
38535
+ value: tableName
38536
+ }], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
38212
38537
  try {
38213
38538
  await this.getClient().send(new CreateTableCommand$1({
38214
38539
  CatalogId: catalogId,
@@ -38216,7 +38541,6 @@ var GlueProvider = class {
38216
38541
  TableInput: this.buildTableInput(tableInput, tableName),
38217
38542
  ...openTableFormatInput !== void 0 && { OpenTableFormatInput: openTableFormatInput }
38218
38543
  }));
38219
- const physicalId = `${databaseName}|${tableName}`;
38220
38544
  this.logger.debug(`Successfully created Glue Table ${logicalId}: ${physicalId}`);
38221
38545
  return {
38222
38546
  physicalId,
@@ -49322,11 +49646,11 @@ var S3TablesProvider = class {
49322
49646
  if (!this.client) this.client = new S3TablesClient(this.providerRegion ? { region: this.providerRegion } : {});
49323
49647
  return this.client;
49324
49648
  }
49325
- async create(logicalId, resourceType, properties) {
49649
+ async create(logicalId, resourceType, properties, context) {
49326
49650
  switch (resourceType) {
49327
49651
  case "AWS::S3Tables::TableBucket": return this.createTableBucket(logicalId, resourceType, properties);
49328
- case "AWS::S3Tables::Namespace": return this.createNamespace(logicalId, resourceType, properties);
49329
- case "AWS::S3Tables::Table": return this.createTable(logicalId, resourceType, properties);
49652
+ case "AWS::S3Tables::Namespace": return this.createNamespace(logicalId, resourceType, properties, context);
49653
+ case "AWS::S3Tables::Table": return this.createTable(logicalId, resourceType, properties, context);
49330
49654
  default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
49331
49655
  }
49332
49656
  }
@@ -49442,7 +49766,7 @@ var S3TablesProvider = class {
49442
49766
  namespaceContinuationToken = namespacesResult.continuationToken;
49443
49767
  } while (namespaceContinuationToken);
49444
49768
  }
49445
- async createNamespace(logicalId, resourceType, properties) {
49769
+ async createNamespace(logicalId, resourceType, properties, context) {
49446
49770
  this.logger.debug(`Creating S3 Tables Namespace ${logicalId}`);
49447
49771
  const tableBucketARN = properties["TableBucketARN"];
49448
49772
  if (!tableBucketARN) throw new ProvisioningError(`TableBucketARN is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
@@ -49451,12 +49775,18 @@ var S3TablesProvider = class {
49451
49775
  if (Array.isArray(rawNs) && rawNs.length > 0 && typeof rawNs[0] === "string") namespaceName = rawNs[0];
49452
49776
  else if (typeof rawNs === "string" && rawNs.length > 0) namespaceName = rawNs;
49453
49777
  if (!namespaceName) throw new ProvisioningError(`Namespace is required for S3 Tables Namespace ${logicalId}`, resourceType, logicalId);
49778
+ const physicalId = packCompositeId(resourceType, logicalId, [{
49779
+ name: "tableBucketARN",
49780
+ value: tableBucketARN
49781
+ }, {
49782
+ name: "namespace",
49783
+ value: namespaceName
49784
+ }], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
49454
49785
  try {
49455
49786
  await this.getClient().send(new CreateNamespaceCommand({
49456
49787
  tableBucketARN,
49457
49788
  namespace: [namespaceName]
49458
49789
  }));
49459
- const physicalId = `${tableBucketARN}|${namespaceName}`;
49460
49790
  this.logger.debug(`Successfully created S3 Tables Namespace ${logicalId}: ${physicalId}`);
49461
49791
  return {
49462
49792
  physicalId,
@@ -49487,7 +49817,7 @@ var S3TablesProvider = class {
49487
49817
  throw new ProvisioningError(`Failed to delete S3 Tables Namespace ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, physicalId, cause);
49488
49818
  }
49489
49819
  }
49490
- async createTable(logicalId, resourceType, properties) {
49820
+ async createTable(logicalId, resourceType, properties, context) {
49491
49821
  this.logger.debug(`Creating S3 Tables Table ${logicalId}`);
49492
49822
  const tableBucketARN = properties["TableBucketARN"];
49493
49823
  if (!tableBucketARN) throw new ProvisioningError(`TableBucketARN is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
@@ -49498,6 +49828,20 @@ var S3TablesProvider = class {
49498
49828
  const format = properties["OpenTableFormat"] ?? properties["Format"];
49499
49829
  if (!format) throw new ProvisioningError(`OpenTableFormat is required for S3 Tables Table ${logicalId}`, resourceType, logicalId);
49500
49830
  const tags = this.cfnTagsToSdkMap(properties["Tags"]);
49831
+ const physicalId = packCompositeId(resourceType, logicalId, [
49832
+ {
49833
+ name: "tableBucketARN",
49834
+ value: tableBucketARN
49835
+ },
49836
+ {
49837
+ name: "namespace",
49838
+ value: namespace
49839
+ },
49840
+ {
49841
+ name: "name",
49842
+ value: name
49843
+ }
49844
+ ], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
49501
49845
  try {
49502
49846
  const response = await this.getClient().send(new CreateTableCommand$2({
49503
49847
  tableBucketARN,
@@ -49506,7 +49850,6 @@ var S3TablesProvider = class {
49506
49850
  format,
49507
49851
  ...tags !== void 0 && { tags }
49508
49852
  }));
49509
- const physicalId = `${tableBucketARN}|${namespace}|${name}`;
49510
49853
  if (!response.tableARN) throw new ProvisioningError(`CreateTable did not return a tableARN for ${logicalId} (${physicalId}) — refusing to record an empty TableARN attribute`, resourceType, logicalId, physicalId);
49511
49854
  const tableARN = response.tableARN;
49512
49855
  this.logger.debug(`Successfully created S3 Tables Table ${logicalId}: ${physicalId}`);
@@ -49726,6 +50069,17 @@ var S3TablesProvider = class {
49726
50069
  if (err instanceof NotFoundException$6) return null;
49727
50070
  throw err;
49728
50071
  }
50072
+ const namespaceRefusal = compositeIdSeparatorRefusal("AWS::S3Tables::Namespace", input.logicalId, [{
50073
+ name: "tableBucketARN",
50074
+ value: identity.tableBucketARN
50075
+ }, {
50076
+ name: "namespace",
50077
+ value: identity.namespace
50078
+ }]);
50079
+ if (namespaceRefusal !== void 0) {
50080
+ this.logger.warn(`${namespaceRefusal} Skipping import.`);
50081
+ return null;
50082
+ }
49729
50083
  return {
49730
50084
  physicalId: `${identity.tableBucketARN}|${identity.namespace}`,
49731
50085
  attributes: {}
@@ -49823,6 +50177,24 @@ var S3TablesProvider = class {
49823
50177
  attributes: { TableARN: tableARN }
49824
50178
  };
49825
50179
  }
50180
+ const tableRefusal = compositeIdSeparatorRefusal("AWS::S3Tables::Table", input.logicalId, [
50181
+ {
50182
+ name: "tableBucketARN",
50183
+ value: identity.tableBucketARN
50184
+ },
50185
+ {
50186
+ name: "namespace",
50187
+ value: identity.namespace
50188
+ },
50189
+ {
50190
+ name: "name",
50191
+ value: identity.name
50192
+ }
50193
+ ]);
50194
+ if (tableRefusal !== void 0) {
50195
+ this.logger.warn(`${tableRefusal} Skipping import.`);
50196
+ return null;
50197
+ }
49826
50198
  return {
49827
50199
  physicalId: `${identity.tableBucketARN}|${identity.namespace}|${identity.name}`,
49828
50200
  attributes: { TableARN: tableARN }
@@ -72732,7 +73104,7 @@ function createMigrateCommand() {
72732
73104
  */
72733
73105
  function buildProgram() {
72734
73106
  const program = new Command();
72735
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.3");
73107
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.5");
72736
73108
  program.hook("preAction", (_thisCommand, actionCommand) => {
72737
73109
  const { profile } = actionCommand.optsWithGlobals();
72738
73110
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;