@go-to-k/cdkd 0.281.7 → 0.281.9

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-B8Qo1vPU.js";
2
+ import { $ as replayWarn, $t as uploadCfnTemplate, A as green, An as normalizeAwsError, At as formatDockerLoginError, B as slowCcOperationTimeoutMs, Bt as resolveApp, C as MULTI_REGION_RECREATE_BLOCKED_TYPES, Cn as ResourceUpdateNotSupportedError, Ct as BOOTSTRAP_MARKER_PREFIX, D as bold, Dt as validateAssetBucketName, E as formatResourceLine, Et as parseBootstrapMarker, F as clearOnUpdateRemoval, Ft as getDockerImageBySourceHash, G as getAccountInfo, Gt as resolveStateBucketWithDefaultAndSource, H as isTerminationProtectionPropagationError, Ht as resolveCaptureObservedState, I as ProviderRegistry, It as Synthesizer, J as normalizeAwsTagsToCfn, Jt as warnDeprecatedNoPrefixCliFlag, K as refStateLookupFromResource, Kt as resolveUseCdkBootstrapAssets, L as findActionableSilentDrops, Lt as synthesisStatusMessage, M as yellow, Mt as runDockerForeground, N as IAMRoleProvider, Nt as runDockerStreaming, O as cyan, Ot as validateContainerRepoName, P as collectInlinePolicyNamesManagedBySiblings, Pt as AssetManifestLoader, Q as readConfigString, Qt as findLargeInlineResources, R as findSilentDropProperties, Rt as getDefaultStateBucketName, S as extractDeploymentEventError, Sn as ResourceTimeoutError, St as AssetModeResolver, T as renderStatefulReason, Tn as StackTerminationProtectionError, Tt as getBootstrapMarkerKey, U as IntrinsicFunctionResolver, Ut as resolveSkipPrefix, V as disableInstanceApiTermination, Vt as resolveAutoAssetStorage, W as cfnRefValueFromPhysicalId, Wt as resolveStateBucketWithDefault, X as assertRegionMatch, Xt as CFN_TEMPLATE_URL_LIMIT, Y as resolveExplicitPhysicalId, Yt as CFN_TEMPLATE_BODY_LIMIT, Z as configStringRefusal, Zt as MIGRATE_TMP_PREFIX, _ as createPreDeleteFinalSnapshot, _t as WorkGraph, a as DeploymentEventsStore, an as AwsClients, at as describeTypeWithThrottleRetry, b as unsupportedFinalSnapshotError, bn as PartialFailureError, bt as loadPublishableAssetManifest, c as replayFailedOperations, cn as setAwsClients, ct as isThrottlingError, d as IMPLICIT_DELETE_DEPENDENCIES, dt as LockManager, en as expectedOwnerParam, et as requireConfigArray, f as computeImplicitDeleteEdges, ft as S3StateBackend, g as ccRoutedFinalSnapshotError, gn as LocalStartServiceError, gt as stringifyValue, h as buildFinalSnapshotIdentifier, hn as LocalMigrateError, ht as AssetPublisher, i as DeploymentEventsReader, in as resolveBucketRegion, it as DiffCalculator, j as red, jn as withErrorHandling, jt as getDockerCmd, k as gray, kt as buildDockerImage, l as replayRollback, lt as DagBuilder, m as PRE_DELETE_SNAPSHOT_TYPES, mn as LocalInvokeBuildError$1, mt as shouldRetainResource, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as processStackMessages, nt as requireConfigString, o as planFailedOps, on as getAwsClients, ot as withRetry, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as DeployCancelledError, pt as rebuildClientForBucketRegion, q as WAFv2WebACLProvider, qt as stateBucketExistenceConfirmed, r as DeployEngine, rt as applyRoleArnIfSet, s as planRollback, sn as resetAwsClients, st as isRetryableTransientError, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as AssemblyReader, tt as requireConfigObject, u as withResourceDeadline, un as CdkdError, ut as TemplateParser, v as isFinalSnapshotError, vn as MissingCdkCliError, vt as buildAssetRedirectMap, w as isStatefulRecreateTargetSync, wn as StackHasActiveImportsError, wt as ensureAssetStorage, x as makeCanonicalizePropertiesFn, xn as ProvisioningError, xt as rewriteTemplateAssetReferences, y as refusesFinalSnapshot, yn as NestedStackChildDirectDestroyError, yt as createAssetRedirectResolver, z as CloudControlProvider, zt as getLegacyStateBucketName } from "./deploy-engine-DQ9kW3Pu.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-Lj_P_wtb.js";
4
+ import { t as ASGProvider } from "./asg-provider-C4zMjWYv.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";
@@ -11746,23 +11746,28 @@ var LambdaLayerVersionProvider = class {
11746
11746
  * ## What this does NOT cover
11747
11747
  *
11748
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
11749
+ * join in the tree". One deliberate exclusion, recorded so the scope is not
11750
11750
  * read as wider than it is:
11751
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
11752
  * - **`intrinsic-function-resolver.ts`'s `|` joins** (the WAFv2 and
11762
11753
  * `AWS::Events::Rule` arms) are out of scope by KIND, not by ownership: they
11763
11754
  * build a `Ref` VALUE for the template resolver, never a physicalId cdkd
11764
11755
  * records and later splits. Nothing decodes them, so an ambiguous join has
11765
11756
  * no decode site to go wrong at.
11757
+ *
11758
+ * ## Guarding the PACKERS is not the whole story
11759
+ *
11760
+ * A packer that refuses ambiguity does nothing about an id an EARLIER binary
11761
+ * already recorded, or one an `import` was handed — and a type whose decoder is
11762
+ * loose will mis-decode both. Issue #1711 is where that was learned:
11763
+ * `AWS::Route53::RecordSet`'s `parseRecordSetCompositeId` accepts any three
11764
+ * NON-EMPTY segments, so a record name carrying two pipes decoded to a
11765
+ * DIFFERENT hosted zone at three sites this helper never sees — an `import`
11766
+ * early accept, an identity-resolution short-circuit, and `delete`'s own parse,
11767
+ * where `cdkd destroy` then reported success over a live record. When adopting
11768
+ * this guard for a new type, audit its DECODE sites in the same change and make
11769
+ * each one cross-check the parsed segments against something it can verify them
11770
+ * with (Route 53 uses the template's own `Name` / `Type`).
11766
11771
  */
11767
11772
  /** The character every composite physicalId in this codebase joins segments with. */
11768
11773
  const COMPOSITE_ID_SEPARATOR = "|";
@@ -33705,10 +33710,10 @@ var Route53Provider = class {
33705
33710
  if (!this.route53Client) this.route53Client = new Route53Client(this.providerRegion ? { region: this.providerRegion } : {});
33706
33711
  return this.route53Client;
33707
33712
  }
33708
- async create(logicalId, resourceType, properties) {
33713
+ async create(logicalId, resourceType, properties, context) {
33709
33714
  switch (resourceType) {
33710
33715
  case "AWS::Route53::HostedZone": return this.createHostedZone(logicalId, resourceType, properties);
33711
- case "AWS::Route53::RecordSet": return this.createRecordSet(logicalId, resourceType, properties);
33716
+ case "AWS::Route53::RecordSet": return this.createRecordSet(logicalId, resourceType, properties, context);
33712
33717
  default: throw new ProvisioningError(`Unsupported resource type: ${resourceType}`, resourceType, logicalId);
33713
33718
  }
33714
33719
  }
@@ -33937,11 +33942,25 @@ var Route53Provider = class {
33937
33942
  default: return;
33938
33943
  }
33939
33944
  }
33940
- async createRecordSet(logicalId, resourceType, properties) {
33945
+ async createRecordSet(logicalId, resourceType, properties, context) {
33941
33946
  this.logger.debug(`Creating Route 53 record set ${logicalId}`);
33942
33947
  const hostedZoneId = await this.resolveHostedZoneId(properties, logicalId, resourceType);
33943
33948
  const recordName = properties["Name"];
33944
33949
  const recordType = properties["Type"];
33950
+ const compositeId = packCompositeId(resourceType, logicalId, [
33951
+ {
33952
+ name: "hostedZoneId",
33953
+ value: hostedZoneId
33954
+ },
33955
+ {
33956
+ name: "recordName",
33957
+ value: recordName
33958
+ },
33959
+ {
33960
+ name: "recordType",
33961
+ value: recordType
33962
+ }
33963
+ ], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
33945
33964
  try {
33946
33965
  const resourceRecordSet = this.buildResourceRecordSet(properties);
33947
33966
  const comment = properties["Comment"];
@@ -33955,7 +33974,6 @@ var Route53Provider = class {
33955
33974
  }]
33956
33975
  }
33957
33976
  }));
33958
- const compositeId = `${hostedZoneId}|${recordName}|${recordType}`;
33959
33977
  this.logger.debug(`Successfully created record set ${logicalId}: ${compositeId}`);
33960
33978
  return {
33961
33979
  physicalId: compositeId,
@@ -33972,6 +33990,20 @@ var Route53Provider = class {
33972
33990
  const hostedZoneId = await this.resolveHostedZoneId(properties, logicalId, resourceType, physicalId);
33973
33991
  const recordName = properties["Name"];
33974
33992
  const recordType = properties["Type"];
33993
+ const compositeId = packCompositeId(resourceType, logicalId, [
33994
+ {
33995
+ name: "hostedZoneId",
33996
+ value: hostedZoneId
33997
+ },
33998
+ {
33999
+ name: "recordName",
34000
+ value: recordName
34001
+ },
34002
+ {
34003
+ name: "recordType",
34004
+ value: recordType
34005
+ }
34006
+ ], { onRefusal: (message) => this.logger.warn(message) });
33975
34007
  try {
33976
34008
  const resourceRecordSet = this.buildResourceRecordSet(properties);
33977
34009
  const comment = properties["Comment"];
@@ -33985,7 +34017,6 @@ var Route53Provider = class {
33985
34017
  }]
33986
34018
  }
33987
34019
  }));
33988
- const compositeId = `${hostedZoneId}|${recordName}|${recordType}`;
33989
34020
  this.logger.debug(`Successfully updated record set ${logicalId}`);
33990
34021
  return {
33991
34022
  physicalId: compositeId,
@@ -34000,7 +34031,8 @@ var Route53Provider = class {
34000
34031
  async deleteRecordSet(logicalId, physicalId, resourceType, properties, context) {
34001
34032
  this.logger.debug(`Deleting Route 53 record set ${logicalId}: ${physicalId}`);
34002
34033
  if (!properties) throw new ProvisioningError(`Properties required to delete record set ${logicalId}`, resourceType, logicalId, physicalId);
34003
- const composite = parseRecordSetCompositeId(physicalId);
34034
+ const parsedComposite = parseRecordSetCompositeId(physicalId);
34035
+ const composite = parsedComposite && this.compositeAgreesWithTemplate(parsedComposite, properties) ? parsedComposite : void 0;
34004
34036
  let hostedZoneId;
34005
34037
  if (composite) hostedZoneId = composite.hostedZoneId;
34006
34038
  else try {
@@ -34433,7 +34465,7 @@ var Route53Provider = class {
34433
34465
  */
34434
34466
  async resolveRecordSetIdentity(physicalId, properties, options) {
34435
34467
  const composite = parseRecordSetCompositeId(physicalId);
34436
- if (composite) return composite;
34468
+ if (composite && (!properties || this.compositeAgreesWithTemplate(composite, properties))) return composite;
34437
34469
  if (!properties) return void 0;
34438
34470
  const name = typeof properties["Name"] === "string" ? properties["Name"] : physicalId;
34439
34471
  const type = properties["Type"];
@@ -34612,6 +34644,52 @@ var Route53Provider = class {
34612
34644
  }
34613
34645
  }
34614
34646
  /**
34647
+ * Does a parsed composite agree with the properties recorded alongside it?
34648
+ *
34649
+ * Called from all THREE sites that split a stored id back apart —
34650
+ * `importRecordSet`'s early accept, `resolveRecordSetIdentity`, and
34651
+ * `deleteRecordSet` — because `parseRecordSetCompositeId` asks only for three
34652
+ * NON-EMPTY segments, which a record NAME carrying two pipes satisfies
34653
+ * (issue #1711).
34654
+ *
34655
+ * Each check is applied only when the template side is a usable string, so an
34656
+ * unresolved intrinsic or an absent field leaves the pre-#1711 behavior
34657
+ * exactly as it was. When BOTH are unusable this returns `true` and the
34658
+ * mis-decode is still reachable — an accepted bound, since the alternative is
34659
+ * refusing every id on a bag cdkd cannot read, and the identity resolution the
34660
+ * fall-through leads to needs those same fields anyway.
34661
+ *
34662
+ * The NAME check is what catches the residual the TYPE check alone leaves: a
34663
+ * name like `a|b|A` on a record whose `Type` really is `A` agrees on the type
34664
+ * and still decodes to the wrong zone. The TYPE check is in turn the sole
34665
+ * discriminator only where the NAME is unusable, which is why both are here.
34666
+ *
34667
+ * **A false REJECTION is what this must not do, and the argument is
34668
+ * REACHABILITY rather than cost.** The cost differs per caller and is not
34669
+ * uniformly small: at `deleteRecordSet` the fall-through re-resolves the zone
34670
+ * with `requireUnambiguousZoneName`, which THROWS on a split-horizon
34671
+ * `HostedZoneName` and would turn a working destroy into a failed one. It
34672
+ * cannot fire, because every composite is packed from the same
34673
+ * `properties['Name']` / `['Type']` the engine records beside it — create,
34674
+ * update and the import canonicalization all use one bag — so a genuine
34675
+ * composite always agrees with the bag it is stored with. `Type` being
34676
+ * updateable does not break that: `update()` returns the RE-PACKED id, which
34677
+ * is recorded together with the new properties. The one real divergence is
34678
+ * cosmetic and absorbed on purpose — `canonicalizeQueryName` makes the name
34679
+ * compare case- and trailing-dot-insensitive, so a template spelling the name
34680
+ * without CDK's dot is not rejected.
34681
+ *
34682
+ * Note two of the three callers pass a STATE-borne bag rather than a template
34683
+ * one; the reachability argument above is what covers them.
34684
+ */
34685
+ compositeAgreesWithTemplate(parsed, properties) {
34686
+ const templateType = properties["Type"];
34687
+ if (typeof templateType === "string" && templateType && parsed.type !== templateType) return false;
34688
+ const templateName = properties["Name"];
34689
+ if (typeof templateName === "string" && templateName && canonicalizeQueryName(parsed.name) !== canonicalizeQueryName(templateName)) return false;
34690
+ return true;
34691
+ }
34692
+ /**
34615
34693
  * Adopt an existing record set (issue #1658).
34616
34694
  *
34617
34695
  * cdkd's physicalId is the composite `<hostedZoneId>|<Name>|<Type>` that
@@ -34654,7 +34732,8 @@ var Route53Provider = class {
34654
34732
  async importRecordSet(input) {
34655
34733
  const known = input.knownPhysicalId;
34656
34734
  if (!known) return null;
34657
- if (parseRecordSetCompositeId(known)) return {
34735
+ const parsedKnown = parseRecordSetCompositeId(known);
34736
+ if (parsedKnown && this.compositeAgreesWithTemplate(parsedKnown, input.properties)) return {
34658
34737
  physicalId: known,
34659
34738
  attributes: {}
34660
34739
  };
@@ -34667,7 +34746,22 @@ var Route53Provider = class {
34667
34746
  };
34668
34747
  const identity = await this.resolveRecordSetIdentity(known, input.properties, { requireUnambiguousZoneName: true });
34669
34748
  if (!identity) return adoptVerbatim("the template does not unambiguously resolve to a hosted zone + Name + Type");
34670
- const compositeId = `${identity.hostedZoneId}|${identity.name}|${identity.type}`;
34749
+ const segments = [
34750
+ {
34751
+ name: "hostedZoneId",
34752
+ value: identity.hostedZoneId
34753
+ },
34754
+ {
34755
+ name: "recordName",
34756
+ value: identity.name
34757
+ },
34758
+ {
34759
+ name: "recordType",
34760
+ value: identity.type
34761
+ }
34762
+ ];
34763
+ if (compositeIdSeparatorRefusal(input.resourceType, input.logicalId, segments) !== void 0) return adoptVerbatim(`the resolved hosted zone + Name + Type would pack an ambiguous id — a segment contains '${"|"}', which cdkd uses as this type's physical-id separator`);
34764
+ const compositeId = packCompositeId(input.resourceType, input.logicalId, segments);
34671
34765
  let observed;
34672
34766
  try {
34673
34767
  observed = await this.readRecordSet(compositeId);
@@ -36671,6 +36765,51 @@ var ServiceDiscoveryProvider = class ServiceDiscoveryProvider {
36671
36765
  }
36672
36766
  };
36673
36767
 
36768
+ //#endregion
36769
+ //#region src/utils/aws-partition.ts
36770
+ /**
36771
+ * AWS partition / URL-suffix derivation, shared across layers.
36772
+ *
36773
+ * Lives in `src/utils/` because it has consumers in two different layers: the
36774
+ * `cdkd local *` command family (which passes a region in once to keep the STS
36775
+ * hop minimal) and the provisioning layer's `AppSyncProvider`, which rebuilds a
36776
+ * child resource's ARN when AWS did not report one.
36777
+ *
36778
+ * It was originally defined in `src/local/ecs-task-resolver.ts`, which
36779
+ * re-exports it so every existing call site is unchanged; a provisioning
36780
+ * provider importing from `src/local/**` would invert the layering.
36781
+ *
36782
+ * NOTE this is the mapping to prefer over `getAccountInfo().partition`
36783
+ * (`src/deployment/intrinsic-function-resolver.ts`), which is hardcoded to
36784
+ * `'aws'` and says so in its own comment.
36785
+ */
36786
+ /**
36787
+ * Derive the AWS partition / URL suffix for an AWS region. Same mapping
36788
+ * CloudFormation applies to `${AWS::Partition}` / `${AWS::URLSuffix}`.
36789
+ */
36790
+ function derivePartitionAndUrlSuffix(region) {
36791
+ if (region.startsWith("cn-")) return {
36792
+ partition: "aws-cn",
36793
+ urlSuffix: "amazonaws.com.cn"
36794
+ };
36795
+ if (region.startsWith("us-gov-")) return {
36796
+ partition: "aws-us-gov",
36797
+ urlSuffix: "amazonaws.com"
36798
+ };
36799
+ if (region.startsWith("us-iso-")) return {
36800
+ partition: "aws-iso",
36801
+ urlSuffix: "c2s.ic.gov"
36802
+ };
36803
+ if (region.startsWith("us-isob-")) return {
36804
+ partition: "aws-iso-b",
36805
+ urlSuffix: "sc2s.sgov.gov"
36806
+ };
36807
+ return {
36808
+ partition: "aws",
36809
+ urlSuffix: "amazonaws.com"
36810
+ };
36811
+ }
36812
+
36674
36813
  //#endregion
36675
36814
  //#region src/provisioning/providers/appsync-provider.ts
36676
36815
  const MUTABLE_GRAPHQL_API_CONFIG_PROPERTIES = [
@@ -36788,6 +36927,111 @@ var AppSyncProvider = class {
36788
36927
  return this.client;
36789
36928
  }
36790
36929
  /**
36930
+ * Build a child-resource ARN from the deploy's REAL partition / region /
36931
+ * account (issue #1681).
36932
+ *
36933
+ * The three child types record an ARN attribute because CloudFormation's
36934
+ * `Ref` for each of them returns the resource ARN (docs-verified 2026-08-12)
36935
+ * and `Fn::GetAtt` serves the same value from the same cached map — this
36936
+ * provider's `getAttribute` always resolves `undefined`, so the recorded
36937
+ * attribute is the ONLY source for both intrinsics.
36938
+ *
36939
+ * Until #1681 those attributes were string-built as
36940
+ * `arn:aws:appsync:*:*:...`, i.e. with literal `*` in the region and account
36941
+ * positions, so every consumer received an ARN AWS cannot resolve. Prefer the
36942
+ * ARN the create response reports (`dataSourceArn` / `resolverArn`); this
36943
+ * helper is for `AWS::AppSync::ApiKey`, whose `CreateApiKey` response carries
36944
+ * no ARN field at all, and as the fallback for the other two when a response
36945
+ * comes back without one.
36946
+ *
36947
+ * `getAccountInfo` is the same STS-backed, process-cached resolver
36948
+ * `CloudControlProvider` uses for its own ARN reconstruction, so this costs
36949
+ * at most one `GetCallerIdentity` per deploy.
36950
+ */
36951
+ async buildAppSyncArn(suffix) {
36952
+ const accountInfo = await getAccountInfo(this.providerRegion ?? await this.getClient().config.region());
36953
+ const { partition } = derivePartitionAndUrlSuffix(accountInfo.region);
36954
+ return `arn:${partition}:appsync:${accountInfo.region}:${accountInfo.accountId}:${suffix}`;
36955
+ }
36956
+ /**
36957
+ * {@link buildAppSyncArn} for the POST-CREATE sites, where throwing is not an
36958
+ * option (issue #1681 PR review).
36959
+ *
36960
+ * Moving the ARN build outside the create `try` stops a failure being
36961
+ * re-labelled as a creation failure — but on its own it does NOT stop the
36962
+ * resource being orphaned: `create()` still throws, the engine still journals
36963
+ * no physicalId for a failed CREATE, and the resource still sits in AWS
36964
+ * untracked (the issue #1710 class). Since the ARN is bookkeeping and the
36965
+ * resource already exists, the honest answer is to never fail the create for
36966
+ * it: warn, omit the attribute, and let `Ref` fall back to the compound id
36967
+ * exactly as it does for an imported child. The resource's next update heals
36968
+ * the record via `childRefAttributes`.
36969
+ */
36970
+ async buildAppSyncArnOrWarn(suffix, logicalId, physicalId) {
36971
+ try {
36972
+ return await this.buildAppSyncArn(suffix);
36973
+ } catch (error) {
36974
+ this.logger.warn(`Created ${logicalId} (${physicalId}) but could not build its ARN attribute: ${error instanceof Error ? error.message : String(error)}. Ref will resolve to the compound physical id until the next update.`);
36975
+ return;
36976
+ }
36977
+ }
36978
+ /**
36979
+ * Rebuild a child type's COMPLETE attribute set from its compound physical id
36980
+ * (issue #1681), for the update path to return.
36981
+ *
36982
+ * This is what heals a record written by a pre-#1681 binary. `update()`
36983
+ * previously returned no attributes, and the deploy engine carries the
36984
+ * existing ones forward when a provider reports none — so without this a
36985
+ * resource created before the fix would keep its `arn:aws:appsync:*:*:...`
36986
+ * placeholder for the rest of its life, since only a REPLACEMENT re-runs
36987
+ * `create()`. `Fn::GetAtt` reads the same cached map, so the placeholder was
36988
+ * breaking that intrinsic too, independently of the `Ref` defect #1681 is
36989
+ * about.
36990
+ *
36991
+ * It must return EVERY key the create path records, not just the ARN: an
36992
+ * `attributes` map returned from `update()` REPLACES the record's map
36993
+ * wholesale rather than merging into it (`deploy-engine.ts`), so a partial
36994
+ * answer would silently drop `Name` / `ApiKey`.
36995
+ *
36996
+ * Returns `undefined` for an id whose arity does not match the type — a
36997
+ * record that malformed cannot be healed by guessing, and carrying the
36998
+ * existing attributes forward is the safe answer.
36999
+ */
37000
+ async childRefAttributes(resourceType, physicalId) {
37001
+ const parts = physicalId.split("|");
37002
+ if (resourceType === "AWS::AppSync::DataSource" && parts.length === 2) {
37003
+ const [apiId, name] = parts;
37004
+ return {
37005
+ DataSourceArn: await this.buildAppSyncArn(`apis/${apiId}/datasources/${name}`),
37006
+ Name: name
37007
+ };
37008
+ }
37009
+ if (resourceType === "AWS::AppSync::Resolver" && parts.length === 3) {
37010
+ const [apiId, typeName, fieldName] = parts;
37011
+ return { ResolverArn: await this.buildAppSyncArn(`apis/${apiId}/types/${typeName}/resolvers/${fieldName}`) };
37012
+ }
37013
+ if (resourceType === "AWS::AppSync::ApiKey" && parts.length === 2) {
37014
+ const [apiId, apiKeyId] = parts;
37015
+ return {
37016
+ ApiKey: apiKeyId,
37017
+ Arn: await this.buildAppSyncArn(`apis/${apiId}/apikey/${apiKeyId}`)
37018
+ };
37019
+ }
37020
+ }
37021
+ /**
37022
+ * The no-replacement update result for a child type, carrying the healed
37023
+ * attribute set (issue #1681). Reporting none is still the correct answer for
37024
+ * an unhealable id — the engine then carries the existing attributes forward.
37025
+ */
37026
+ async childUpdateResult(resourceType, physicalId) {
37027
+ const attributes = await this.childRefAttributes(resourceType, physicalId);
37028
+ return {
37029
+ physicalId,
37030
+ wasReplaced: false,
37031
+ ...attributes && { attributes }
37032
+ };
37033
+ }
37034
+ /**
36791
37035
  * Lazy S3 client for the `*S3Location` properties (Resolver
36792
37036
  * `CodeS3Location` / `RequestMappingTemplateS3Location` /
36793
37037
  * `ResponseMappingTemplateS3Location`, GraphQLSchema
@@ -36977,10 +37221,7 @@ var AppSyncProvider = class {
36977
37221
  "EventBridgeConfig",
36978
37222
  "RelationalDatabaseConfig",
36979
37223
  "MetricsConfig"
36980
- ].some((key) => (key in properties || key in previousProperties) && !this.deepEqual(properties[key], previousProperties[key]))) return {
36981
- physicalId,
36982
- wasReplaced: false
36983
- };
37224
+ ].some((key) => (key in properties || key in previousProperties) && !this.deepEqual(properties[key], previousProperties[key]))) return this.childUpdateResult(resourceType, physicalId);
36984
37225
  const input = {
36985
37226
  apiId,
36986
37227
  name,
@@ -36992,10 +37233,7 @@ var AppSyncProvider = class {
36992
37233
  } catch (error) {
36993
37234
  throw this.wrapUpdateError(error, resourceType, logicalId, physicalId, "DataSource");
36994
37235
  }
36995
- return {
36996
- physicalId,
36997
- wasReplaced: false
36998
- };
37236
+ return this.childUpdateResult(resourceType, physicalId);
36999
37237
  }
37000
37238
  async updateResolver(logicalId, physicalId, resourceType, properties, previousProperties) {
37001
37239
  for (const field of [
@@ -37025,10 +37263,7 @@ var AppSyncProvider = class {
37025
37263
  "CodeS3Location",
37026
37264
  "RequestMappingTemplateS3Location",
37027
37265
  "ResponseMappingTemplateS3Location"
37028
- ].some((key) => (key in properties || key in previousProperties) && !this.deepEqual(properties[key], previousProperties[key]))) return {
37029
- physicalId,
37030
- wasReplaced: false
37031
- };
37266
+ ].some((key) => (key in properties || key in previousProperties) && !this.deepEqual(properties[key], previousProperties[key]))) return this.childUpdateResult(resourceType, physicalId);
37032
37267
  const input = {
37033
37268
  apiId,
37034
37269
  typeName,
@@ -37040,10 +37275,7 @@ var AppSyncProvider = class {
37040
37275
  } catch (error) {
37041
37276
  throw this.wrapUpdateError(error, resourceType, logicalId, physicalId, "Resolver");
37042
37277
  }
37043
- return {
37044
- physicalId,
37045
- wasReplaced: false
37046
- };
37278
+ return this.childUpdateResult(resourceType, physicalId);
37047
37279
  }
37048
37280
  async updateApiKey(logicalId, physicalId, resourceType, properties, previousProperties) {
37049
37281
  if (properties["ApiId"] !== void 0 && previousProperties["ApiId"] !== void 0 && properties["ApiId"] !== previousProperties["ApiId"]) throw new ResourceUpdateNotSupportedError(resourceType, logicalId, "AWS AppSync ApiKey.ApiId is immutable — destroy + redeploy to change");
@@ -37053,10 +37285,7 @@ var AppSyncProvider = class {
37053
37285
  const oldDesc = previousProperties["Description"];
37054
37286
  const newExp = properties["Expires"];
37055
37287
  const oldExp = previousProperties["Expires"];
37056
- if (newDesc === oldDesc && newExp === oldExp) return {
37057
- physicalId,
37058
- wasReplaced: false
37059
- };
37288
+ if (newDesc === oldDesc && newExp === oldExp) return this.childUpdateResult(resourceType, physicalId);
37060
37289
  const input = {
37061
37290
  apiId,
37062
37291
  id: apiKeyId
@@ -37068,10 +37297,7 @@ var AppSyncProvider = class {
37068
37297
  } catch (error) {
37069
37298
  throw this.wrapUpdateError(error, resourceType, logicalId, physicalId, "ApiKey");
37070
37299
  }
37071
- return {
37072
- physicalId,
37073
- wasReplaced: false
37074
- };
37300
+ return this.childUpdateResult(resourceType, physicalId);
37075
37301
  }
37076
37302
  /**
37077
37303
  * Apply a Tags diff to a GraphqlApi via TagResource / UntagResource.
@@ -37733,6 +37959,7 @@ var AppSyncProvider = class {
37733
37959
  name: "name",
37734
37960
  value: name
37735
37961
  }], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
37962
+ let reportedArn;
37736
37963
  try {
37737
37964
  const input = {
37738
37965
  apiId,
@@ -37740,19 +37967,20 @@ var AppSyncProvider = class {
37740
37967
  type
37741
37968
  };
37742
37969
  this.applyDataSourceConfig(input, resourceType, logicalId, properties);
37743
- await this.getClient().send(new CreateDataSourceCommand(input));
37970
+ reportedArn = (await this.getClient().send(new CreateDataSourceCommand(input))).dataSource?.dataSourceArn;
37744
37971
  this.logger.debug(`Successfully created DataSource ${logicalId}: ${physicalId}`);
37745
- return {
37746
- physicalId,
37747
- attributes: {
37748
- DataSourceArn: `arn:aws:appsync:*:*:apis/${apiId}/datasources/${name}`,
37749
- Name: name
37750
- }
37751
- };
37752
37972
  } catch (error) {
37753
37973
  const cause = error instanceof Error ? error : void 0;
37754
37974
  throw new ProvisioningError(`Failed to create DataSource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
37755
37975
  }
37976
+ const dataSourceArn = reportedArn ?? await this.buildAppSyncArnOrWarn(`apis/${apiId}/datasources/${name}`, logicalId, physicalId);
37977
+ return {
37978
+ physicalId,
37979
+ attributes: {
37980
+ ...dataSourceArn !== void 0 && { DataSourceArn: dataSourceArn },
37981
+ Name: name
37982
+ }
37983
+ };
37756
37984
  }
37757
37985
  async deleteDataSource(logicalId, physicalId, resourceType, context) {
37758
37986
  this.logger.debug(`Deleting DataSource ${logicalId}: ${physicalId}`);
@@ -37797,6 +38025,7 @@ var AppSyncProvider = class {
37797
38025
  value: fieldName
37798
38026
  }
37799
38027
  ], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
38028
+ let reportedArn;
37800
38029
  try {
37801
38030
  const input = {
37802
38031
  apiId,
@@ -37804,16 +38033,17 @@ var AppSyncProvider = class {
37804
38033
  fieldName
37805
38034
  };
37806
38035
  await this.applyResolverConfig(input, resourceType, logicalId, properties);
37807
- await this.getClient().send(new CreateResolverCommand(input));
38036
+ reportedArn = (await this.getClient().send(new CreateResolverCommand(input))).resolver?.resolverArn;
37808
38037
  this.logger.debug(`Successfully created Resolver ${logicalId}: ${physicalId}`);
37809
- return {
37810
- physicalId,
37811
- attributes: { ResolverArn: `arn:aws:appsync:*:*:apis/${apiId}/types/${typeName}/resolvers/${fieldName}` }
37812
- };
37813
38038
  } catch (error) {
37814
38039
  const cause = error instanceof Error ? error : void 0;
37815
38040
  throw new ProvisioningError(`Failed to create Resolver ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
37816
38041
  }
38042
+ const resolverArn = reportedArn ?? await this.buildAppSyncArnOrWarn(`apis/${apiId}/types/${typeName}/resolvers/${fieldName}`, logicalId, physicalId);
38043
+ return {
38044
+ physicalId,
38045
+ attributes: { ...resolverArn !== void 0 && { ResolverArn: resolverArn } }
38046
+ };
37817
38047
  }
37818
38048
  async deleteResolver(logicalId, physicalId, resourceType, context) {
37819
38049
  this.logger.debug(`Deleting Resolver ${logicalId}: ${physicalId}`);
@@ -37855,17 +38085,19 @@ var AppSyncProvider = class {
37855
38085
  const cause = error instanceof Error ? error : void 0;
37856
38086
  throw new ProvisioningError(`Failed to create ApiKey ${logicalId}: ${error instanceof Error ? error.message : String(error)}`, resourceType, logicalId, void 0, cause);
37857
38087
  }
38088
+ const physicalId = packCompositeId(resourceType, logicalId, [{
38089
+ name: "apiId",
38090
+ value: apiId
38091
+ }, {
38092
+ name: "apiKeyId",
38093
+ value: apiKeyId
38094
+ }], context?.replayingState === true ? { onRefusal: (message) => this.logger.warn(message) } : void 0);
38095
+ const apiKeyArn = await this.buildAppSyncArnOrWarn(`apis/${apiId}/apikey/${apiKeyId}`, logicalId, physicalId);
37858
38096
  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),
38097
+ physicalId,
37866
38098
  attributes: {
37867
38099
  ApiKey: apiKeyId,
37868
- Arn: `arn:aws:appsync:*:*:apis/${apiId}/apikeys/${apiKeyId}`
38100
+ ...apiKeyArn !== void 0 && { Arn: apiKeyArn }
37869
38101
  }
37870
38102
  };
37871
38103
  }
@@ -64534,34 +64766,6 @@ function notFoundError$1(target, stack, resources) {
64534
64766
 
64535
64767
  //#endregion
64536
64768
  //#region src/local/ecs-task-resolver.ts
64537
- /**
64538
- * Derive the AWS partition / URL suffix for an AWS region. Same mapping
64539
- * CloudFormation applies to `${AWS::Partition}` / `${AWS::URLSuffix}`.
64540
- * Exported so the CLI can keep the STS hop minimal — caller passes the
64541
- * region in once, this returns the matching partition + suffix.
64542
- */
64543
- function derivePartitionAndUrlSuffix(region) {
64544
- if (region.startsWith("cn-")) return {
64545
- partition: "aws-cn",
64546
- urlSuffix: "amazonaws.com.cn"
64547
- };
64548
- if (region.startsWith("us-gov-")) return {
64549
- partition: "aws-us-gov",
64550
- urlSuffix: "amazonaws.com"
64551
- };
64552
- if (region.startsWith("us-iso-")) return {
64553
- partition: "aws-iso",
64554
- urlSuffix: "c2s.ic.gov"
64555
- };
64556
- if (region.startsWith("us-isob-")) return {
64557
- partition: "aws-iso-b",
64558
- urlSuffix: "sc2s.sgov.gov"
64559
- };
64560
- return {
64561
- partition: "aws",
64562
- urlSuffix: "amazonaws.com"
64563
- };
64564
- }
64565
64769
  function detectEcsImageResolutionNeeds(stack) {
64566
64770
  const resources = stack.template.Resources ?? {};
64567
64771
  let needsPseudoParameters = false;
@@ -73207,7 +73411,7 @@ function createMigrateCommand() {
73207
73411
  */
73208
73412
  function buildProgram() {
73209
73413
  const program = new Command();
73210
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.7");
73414
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.281.9");
73211
73415
  program.hook("preAction", (_thisCommand, actionCommand) => {
73212
73416
  const { profile } = actionCommand.optsWithGlobals();
73213
73417
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;