@go-to-k/cdkd 0.278.37 → 0.278.39

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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-bEhHOGVR.js";
2
+ import { $ as describeTypeWithThrottleRetry, $t as AwsClients, A as red, At as Synthesizer, B as isTerminationProtectionPropagationError, Bt as resolveUseCdkBootstrapAssets, C as isStatefulRecreateTargetSync, Ct as buildDockerImage, D as cyan, Dt as runDockerStreaming, E as bold, Et as runDockerForeground, F as ProviderRegistry, Ft as resolveAutoAssetStorage, G as normalizeAwsTagsToCfn, Gt as MIGRATE_TMP_PREFIX, H as cfnRefValueFromPhysicalId, Ht as warnDeprecatedNoPrefixCliFlag, I as findActionableSilentDrops, It as resolveCaptureObservedState, J as readConfigString, Jt as expectedOwnerParam, K as resolveExplicitPhysicalId, Kt as findLargeInlineResources, L as CloudControlProvider, Lt as resolveSkipPrefix, M as IAMRoleProvider, Mt as getDefaultStateBucketName, N as collectInlinePolicyNamesManagedBySiblings, Nt as getLegacyStateBucketName, O as gray, Ot as AssetManifestLoader, P as clearOnUpdateRemoval, Pt as resolveApp, Q as DiffCalculator, Qt as resolveBucketRegion, R as slowCcOperationTimeoutMs, Rt as resolveStateBucketWithDefault, S as MULTI_REGION_RECREATE_BLOCKED_TYPES, St as validateContainerRepoName, T as formatResourceLine, Tn as withErrorHandling, Tt as getDockerCmd, U as refStateLookupFromResource, Ut as CFN_TEMPLATE_BODY_LIMIT, V as IntrinsicFunctionResolver, Vt as stateBucketExistenceConfirmed, W as WAFv2WebACLProvider, Wt as CFN_TEMPLATE_URL_LIMIT, X as requireConfigString, Xt as processStackMessages, Y as requireConfigArray, Yt as AssemblyReader, Z as applyRoleArnIfSet, _ as createPreDeleteFinalSnapshot, _n as ResourceUpdateNotSupportedError, _t as BOOTSTRAP_MARKER_PREFIX, a as DeploymentEventsStore, at as LockManager, b as unsupportedFinalSnapshotError, bt as parseBootstrapMarker, c as replayFailedOperations, cn as LocalInvokeBuildError$1, ct as shouldRetainResource, d as IMPLICIT_DELETE_DEPENDENCIES, dt as WorkGraph, en as getAwsClients, et as withRetry, f as computeImplicitDeleteEdges, fn as MissingCdkCliError, ft as buildAssetRedirectMap, g as ccRoutedFinalSnapshotError, gn as ResourceTimeoutError, gt as AssetModeResolver, h as buildFinalSnapshotIdentifier, hn as ProvisioningError, ht as rewriteTemplateAssetReferences, i as DeploymentEventsReader, in as CdkdError, it as TemplateParser, j as yellow, jt as synthesisStatusMessage, k as green, kt as getDockerImageBySourceHash, l as replayRollback, ln as LocalMigrateError, lt as AssetPublisher, m as PRE_DELETE_SNAPSHOT_TYPES, mn as PartialFailureError, mt as loadPublishableAssetManifest, n as DEFAULT_RESOURCE_WARN_AFTER_MS, nn as setAwsClients, nt as isThrottlingError, o as planFailedOps, ot as S3StateBackend, p as ATOMIC_FINAL_SNAPSHOT_TYPES, pn as NestedStackChildDirectDestroyError, pt as createAssetRedirectResolver, q as assertRegionMatch, qt as uploadCfnTemplate, r as DeployEngine, rt as DagBuilder, s as planRollback, sn as DeployCancelledError, st as rebuildClientForBucketRegion, t as DEFAULT_RESOURCE_TIMEOUT_MS, tn as resetAwsClients, tt as isRetryableTransientError, u as withResourceDeadline, un as LocalStartServiceError, ut as stringifyValue, v as isFinalSnapshotError, vn as StackHasActiveImportsError, vt as ensureAssetStorage, w as renderStatefulReason, wn as normalizeAwsError, wt as formatDockerLoginError, x as extractDeploymentEventError, xt as validateAssetBucketName, y as refusesFinalSnapshot, yn as StackTerminationProtectionError, yt as getBootstrapMarkerKey, z as disableInstanceApiTermination, zt as resolveStateBucketWithDefaultAndSource } from "./deploy-engine-BJVpWi-B.js";
3
3
  import { a as getLiveRenderer, c as PATTERN_B_RESOURCE_TYPES, d as generateResourceNameWithFallback, f as withSkipPrefix, i as runStackBuffered, n as getLogger, o as PATTERN_B_NAME_OPTIONS, p as withStackName, s as PATTERN_B_NAME_PROPERTIES, u as generateResourceName } from "./logger-BYMEE-BS.js";
4
- import { t as ASGProvider } from "./asg-provider-BTeSlton.js";
4
+ import { t as ASGProvider } from "./asg-provider-CJJC8plY.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";
8
- import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachRolePolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreatePolicyCommand, CreatePolicyVersionCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeletePolicyCommand, DeletePolicyVersionCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachRolePolicyCommand, DetachUserPolicyCommand, GetAccessKeyLastUsedCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetPolicyCommand, GetPolicyVersionCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListUserPoliciesCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagPolicyCommand, TagUserCommand, UntagPolicyCommand, UntagUserCommand, UpdateAccessKeyCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
8
+ import { AddRoleToInstanceProfileCommand, AddUserToGroupCommand, AttachGroupPolicyCommand, AttachRolePolicyCommand, AttachUserPolicyCommand, CreateAccessKeyCommand, CreateGroupCommand, CreateInstanceProfileCommand, CreateLoginProfileCommand, CreatePolicyCommand, CreatePolicyVersionCommand, CreateUserCommand, DeleteAccessKeyCommand, DeleteGroupCommand, DeleteGroupPolicyCommand, DeleteInstanceProfileCommand, DeleteLoginProfileCommand, DeletePolicyCommand, DeletePolicyVersionCommand, DeleteRolePolicyCommand, DeleteUserCommand, DeleteUserPermissionsBoundaryCommand, DeleteUserPolicyCommand, DetachGroupPolicyCommand, DetachRolePolicyCommand, DetachUserPolicyCommand, GetAccessKeyLastUsedCommand, GetGroupCommand, GetGroupPolicyCommand, GetInstanceProfileCommand, GetPolicyCommand, GetPolicyVersionCommand, GetRoleCommand, GetRolePolicyCommand, GetUserCommand, GetUserPolicyCommand, IAMClient, ListAccessKeysCommand, ListAttachedGroupPoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, ListGroupPoliciesCommand, ListGroupsForUserCommand, ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListUserPoliciesCommand, NoSuchEntityException, PutGroupPolicyCommand, PutRolePolicyCommand, PutUserPermissionsBoundaryCommand, PutUserPolicyCommand, RemoveRoleFromInstanceProfileCommand, RemoveUserFromGroupCommand, TagPolicyCommand, TagUserCommand, UntagPolicyCommand, UntagUserCommand, UpdateAccessKeyCommand, UpdateLoginProfileCommand } from "@aws-sdk/client-iam";
9
9
  import { CreateQueueCommand, DeleteQueueCommand, GetQueueAttributesCommand, GetQueueUrlCommand, ListQueueTagsCommand, QueueDoesNotExist, SQSClient, SetQueueAttributesCommand, TagQueueCommand, UntagQueueCommand } from "@aws-sdk/client-sqs";
10
10
  import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, InvalidParameterException, ListSubscriptionsByTopicCommand, ListTagsForResourceCommand, ListTopicsCommand, NotFoundException, SNSClient, SetTopicAttributesCommand, SubscribeCommand, TagResourceCommand, UnsubscribeCommand, UntagResourceCommand } from "@aws-sdk/client-sns";
11
11
  import { AddPermissionCommand, CreateEventSourceMappingCommand, CreateFunctionCommand, CreateFunctionUrlConfigCommand, DeleteEventSourceMappingCommand, DeleteFunctionCommand, DeleteFunctionConcurrencyCommand, DeleteFunctionEventInvokeConfigCommand, DeleteFunctionUrlConfigCommand, DeleteLayerVersionCommand, GetEventSourceMappingCommand, GetFunctionCommand, GetFunctionConcurrencyCommand, GetFunctionEventInvokeConfigCommand, GetFunctionRecursionConfigCommand, GetFunctionUrlConfigCommand, GetLayerVersionByArnCommand, GetPolicyCommand as GetPolicyCommand$1, LambdaClient, ListTagsCommand, PublishLayerVersionCommand, PutFunctionConcurrencyCommand, PutFunctionEventInvokeConfigCommand, PutFunctionRecursionConfigCommand, RemovePermissionCommand, ResourceNotFoundException, TagResourceCommand as TagResourceCommand$1, UntagResourceCommand as UntagResourceCommand$1, UpdateEventSourceMappingCommand, UpdateFunctionCodeCommand, UpdateFunctionConfigurationCommand, UpdateFunctionUrlConfigCommand, waitUntilFunctionUpdatedV2 } from "@aws-sdk/client-lambda";
@@ -7600,27 +7600,12 @@ var SNSTopicProvider = class {
7600
7600
  AttributeValue: dataProtectionPolicy
7601
7601
  }));
7602
7602
  }
7603
- if (properties["DeliveryStatusLogging"]) {
7604
- const loggingConfigs = properties["DeliveryStatusLogging"];
7605
- for (const config of loggingConfigs) {
7606
- const protocol = normalizeDeliveryStatusProtocolOrThrow(config["Protocol"], logicalId);
7607
- if (config["SuccessFeedbackRoleArn"]) await this.snsClient.send(new SetTopicAttributesCommand({
7608
- TopicArn: topicArn,
7609
- AttributeName: `${protocol}SuccessFeedbackRoleArn`,
7610
- AttributeValue: config["SuccessFeedbackRoleArn"]
7611
- }));
7612
- if (config["SuccessFeedbackSampleRate"]) await this.snsClient.send(new SetTopicAttributesCommand({
7613
- TopicArn: topicArn,
7614
- AttributeName: `${protocol}SuccessFeedbackSampleRate`,
7615
- AttributeValue: stringifyValue(config["SuccessFeedbackSampleRate"])
7616
- }));
7617
- if (config["FailureFeedbackRoleArn"]) await this.snsClient.send(new SetTopicAttributesCommand({
7618
- TopicArn: topicArn,
7619
- AttributeName: `${protocol}FailureFeedbackRoleArn`,
7620
- AttributeValue: config["FailureFeedbackRoleArn"]
7621
- }));
7622
- }
7623
- }
7603
+ const deliveryStatusAttributes = buildDeliveryStatusAttributeMap(properties["DeliveryStatusLogging"], logicalId, "throw");
7604
+ for (const [attributeName, attributeValue] of deliveryStatusAttributes) await this.snsClient.send(new SetTopicAttributesCommand({
7605
+ TopicArn: topicArn,
7606
+ AttributeName: attributeName,
7607
+ AttributeValue: attributeValue
7608
+ }));
7624
7609
  if (Array.isArray(properties["Subscription"])) {
7625
7610
  const subscriptions = properties["Subscription"];
7626
7611
  for (const sub of subscriptions) await this.subscribeInline(topicArn, sub, logicalId);
@@ -7727,24 +7712,24 @@ var SNSTopicProvider = class {
7727
7712
  }
7728
7713
  }
7729
7714
  if (JSON.stringify(properties["DeliveryStatusLogging"]) !== JSON.stringify(previousProperties["DeliveryStatusLogging"])) {
7730
- const loggingConfigs = properties["DeliveryStatusLogging"] || [];
7731
- for (const config of loggingConfigs) {
7732
- const protocol = normalizeDeliveryStatusProtocolOrThrow(config["Protocol"], logicalId);
7733
- if (config["SuccessFeedbackRoleArn"]) await this.snsClient.send(new SetTopicAttributesCommand({
7734
- TopicArn: physicalId,
7735
- AttributeName: `${protocol}SuccessFeedbackRoleArn`,
7736
- AttributeValue: config["SuccessFeedbackRoleArn"]
7737
- }));
7738
- if (config["SuccessFeedbackSampleRate"]) await this.snsClient.send(new SetTopicAttributesCommand({
7715
+ const desiredAttributes = buildDeliveryStatusAttributeMap(properties["DeliveryStatusLogging"], logicalId, "throw");
7716
+ const previousAttributes = buildDeliveryStatusAttributeMap(previousProperties["DeliveryStatusLogging"], logicalId, "skip");
7717
+ for (const [attributeName, attributeValue] of desiredAttributes) {
7718
+ if (previousAttributes.get(attributeName) === attributeValue) continue;
7719
+ await this.snsClient.send(new SetTopicAttributesCommand({
7739
7720
  TopicArn: physicalId,
7740
- AttributeName: `${protocol}SuccessFeedbackSampleRate`,
7741
- AttributeValue: stringifyValue(config["SuccessFeedbackSampleRate"])
7721
+ AttributeName: attributeName,
7722
+ AttributeValue: attributeValue
7742
7723
  }));
7743
- if (config["FailureFeedbackRoleArn"]) await this.snsClient.send(new SetTopicAttributesCommand({
7724
+ }
7725
+ for (const attributeName of previousAttributes.keys()) {
7726
+ if (desiredAttributes.has(attributeName)) continue;
7727
+ await this.snsClient.send(new SetTopicAttributesCommand({
7744
7728
  TopicArn: physicalId,
7745
- AttributeName: `${protocol}FailureFeedbackRoleArn`,
7746
- AttributeValue: config["FailureFeedbackRoleArn"]
7729
+ AttributeName: attributeName,
7730
+ AttributeValue: deliveryStatusRemovalResetValue(attributeName)
7747
7731
  }));
7732
+ this.logger.debug(`Reset removed delivery status attribute ${attributeName} for topic ${physicalId}`);
7748
7733
  }
7749
7734
  }
7750
7735
  if (JSON.stringify(properties["Subscription"]) !== JSON.stringify(previousProperties["Subscription"])) await this.reconcileInlineSubscriptions(physicalId, previousProperties["Subscription"] || [], properties["Subscription"] || [], logicalId);
@@ -8075,6 +8060,76 @@ const SNS_DELIVERY_STATUS_PROTOCOLS = [
8075
8060
  "SQS"
8076
8061
  ];
8077
8062
  /**
8063
+ * The per-protocol attribute suffixes an inline `DeliveryStatusLogging`
8064
+ * entry maps to (`<Protocol><Suffix>` flat attribute names on the topic).
8065
+ */
8066
+ const SNS_DELIVERY_STATUS_ATTRIBUTE_SUFFIXES = [
8067
+ "SuccessFeedbackRoleArn",
8068
+ "SuccessFeedbackSampleRate",
8069
+ "FailureFeedbackRoleArn"
8070
+ ];
8071
+ /**
8072
+ * Flatten a CFn `DeliveryStatusLogging` array into the AWS flat attribute
8073
+ * map (`{LambdaSuccessFeedbackRoleArn: '...', ...}`) that
8074
+ * `SetTopicAttributes` consumes one entry at a time.
8075
+ *
8076
+ * Field presence is `!= null`, NOT truthiness, so
8077
+ * `SuccessFeedbackSampleRate: 0` is sent as `'0'` instead of being
8078
+ * silently skipped (the pre-#1160 truthiness gates dropped a 0 sample
8079
+ * rate on create and update alike).
8080
+ *
8081
+ * `onMalformed` picks the failure mode for a malformed container / entry
8082
+ * / unknown protocol: `'throw'` for the DESIRED side (a template error
8083
+ * must fail fast, naming the offending value), `'skip'` for the PREVIOUS
8084
+ * side (cdkd state — refusing a value an older binary recorded would make
8085
+ * the stack permanently undeployable, and an entry whose protocol cannot
8086
+ * be canonicalized maps to no attribute names anyway).
8087
+ */
8088
+ function buildDeliveryStatusAttributeMap(logging, logicalId, onMalformed) {
8089
+ const map = /* @__PURE__ */ new Map();
8090
+ if (logging == null) return map;
8091
+ if (!Array.isArray(logging)) {
8092
+ if (onMalformed === "skip") return map;
8093
+ throw new Error(`SNS topic ${logicalId}: DeliveryStatusLogging must be an array of {Protocol, ...} objects, got ${JSON.stringify(logging)}`);
8094
+ }
8095
+ for (const entry of logging) {
8096
+ if (entry == null || typeof entry !== "object" || Array.isArray(entry)) {
8097
+ if (onMalformed === "skip") continue;
8098
+ throw new Error(`SNS topic ${logicalId}: DeliveryStatusLogging entries must be {Protocol, ...} objects, got ${JSON.stringify(entry)}`);
8099
+ }
8100
+ const config = entry;
8101
+ let protocol;
8102
+ if (onMalformed === "throw") protocol = normalizeDeliveryStatusProtocolOrThrow(config["Protocol"], logicalId);
8103
+ else {
8104
+ const normalized = normalizeDeliveryStatusProtocol(config["Protocol"]);
8105
+ if (normalized === void 0) continue;
8106
+ protocol = normalized;
8107
+ }
8108
+ for (const suffix of SNS_DELIVERY_STATUS_ATTRIBUTE_SUFFIXES) {
8109
+ const value = config[suffix];
8110
+ if (value == null) continue;
8111
+ map.set(`${protocol}${suffix}`, stringifyValue(value));
8112
+ }
8113
+ }
8114
+ return map;
8115
+ }
8116
+ /**
8117
+ * The `SetTopicAttributes` value that resets a REMOVED per-protocol
8118
+ * delivery status attribute to its CloudFormation-removal state.
8119
+ *
8120
+ * Live CFn A/B (2026-08-10, issue #1160): removing `DeliveryStatusLogging`
8121
+ * from a template clears both RoleArn attributes (AWS accepts `''` as the
8122
+ * clear sentinel — the attribute disappears from `GetTopicAttributes`) and
8123
+ * explicitly resets `SuccessFeedbackSampleRate` to `'0'`. The `''`
8124
+ * sentinel is REJECTED for the sample rate ("value provided is not an
8125
+ * integer between 0-100"), so the numeric reset is the only wire shape
8126
+ * that clears it — the `'0'` attribute itself remains visible on the
8127
+ * topic afterwards, exactly as it does after a CloudFormation removal.
8128
+ */
8129
+ function deliveryStatusRemovalResetValue(attributeName) {
8130
+ return attributeName.endsWith("SuccessFeedbackSampleRate") ? "0" : "";
8131
+ }
8132
+ /**
8078
8133
  * Map a (possibly-mixed-case) protocol string from a CFn template to the
8079
8134
  * canonical PascalCase prefix AWS expects on `${Protocol}<Suffix>`
8080
8135
  * attribute names.
@@ -49069,6 +49124,208 @@ function isPlainObject(value) {
49069
49124
  return typeof value === "object" && value !== null;
49070
49125
  }
49071
49126
 
49127
+ //#endregion
49128
+ //#region src/analyzer/drift-principal-normalize.ts
49129
+ /**
49130
+ * IAM principal-form canonicalization for the drift comparator (issue #1515).
49131
+ *
49132
+ * AWS renders an IAM role / user principal inside a resource policy in TWO
49133
+ * forms and picks between them on its own schedule:
49134
+ *
49135
+ * - the ARN — `arn:aws:iam::<acct>:role/<name>`
49136
+ * - the principal's UNIQUE ID — `AROA…` (role) / `AIDA…` (user)
49137
+ *
49138
+ * The unique-id form is substituted when the referenced principal is
49139
+ * transiently unresolvable at write time (it is being created or replaced) and
49140
+ * AWS re-renders the ARN once it resolves. cdkd's deploy-time
49141
+ * `observedProperties` capture stores whichever form the read returned at that
49142
+ * moment, so a later `cdkd drift` can compare `AROA…` against the ARN for the
49143
+ * very same principal and report PERMANENT phantom drift — `--revert` writes
49144
+ * the recorded form back, AWS re-canonicalizes on write, and the next run
49145
+ * reports the identical difference (the class of issues #1096 / #1498, on a
49146
+ * different property).
49147
+ *
49148
+ * Unlike the order-normalization passes in `drift-normalize.ts`, this one
49149
+ * CANNOT be pure: the mapping between the two forms is not derivable from the
49150
+ * string. So the shape detection here is pure and an injected
49151
+ * {@link PrincipalUniqueIdResolver} does the one lookup, which lets the whole
49152
+ * pass be unit-tested without AWS and keeps the API cost at zero for the
49153
+ * overwhelmingly common case — a policy with no unique-id principal makes NO
49154
+ * AWS call at all.
49155
+ *
49156
+ * Both sides are rewritten to the ARN form, never one side only. The race that
49157
+ * produces the mismatch is symmetric (the 2026-08-10 evidence on the
49158
+ * `drift-revert` fixture had the baseline holding `AROA…` and AWS the ARN, but
49159
+ * the reverse ordering is equally reachable), and normalizing a single side is
49160
+ * the mistake `drift-normalize.ts`'s header already records for the unordered
49161
+ * passes — it manufactures drift on the `properties`-fallback baseline.
49162
+ *
49163
+ * FAILING IS VISIBLE, never silent: when the resolver cannot answer (the role
49164
+ * was deleted — which is exactly when AWS keeps the unique id forever — the
49165
+ * caller lacks `iam:GetRole`, or the ARN names another ACCOUNT / IAM PATH, for
49166
+ * which the name-only `GetRole` API would otherwise answer about the caller's
49167
+ * own same-named role), both sides are left untouched and the drift is still
49168
+ * reported. This pass only ever removes a difference it has PROVEN to be two
49169
+ * spellings of one principal; it can never hide a real principal change.
49170
+ *
49171
+ * One downstream note: because the comparator's inputs are canonicalized,
49172
+ * `cdkd drift --accept` records the ARN form for a policy subtree that drifts
49173
+ * for some OTHER reason while also carrying a canonicalized principal — i.e.
49174
+ * the accepted baseline can hold a spelling AWS did not literally return. That
49175
+ * is benign (it is the form AWS re-canonicalizes to, and a later run
49176
+ * normalizes either way) but it is a real difference from "state records
49177
+ * exactly what AWS said", so it is written down rather than left to be
49178
+ * rediscovered.
49179
+ *
49180
+ * Known bound: a policy carried as a JSON STRING rather than a parsed object is
49181
+ * not walked. Re-serializing a parsed string would risk changing its formatting
49182
+ * and manufacturing a different phantom drift, so the pass is scoped to the
49183
+ * object shape — which is what `AWS::S3::BucketPolicy` and the other
49184
+ * `PolicyDocument`-typed properties carry in cdkd state.
49185
+ */
49186
+ /**
49187
+ * An IAM unique id as it appears in a rendered policy. The prefix set is
49188
+ * deliberately narrow — `AROA` (role) and `AIDA` (user) are the only two that
49189
+ * can stand in for an ARN at a `Principal.AWS` position; `AGPA` (group),
49190
+ * `AKIA` (access key) and friends are never rendered there.
49191
+ */
49192
+ const PRINCIPAL_UNIQUE_ID_RE = /^A(?:ROA|IDA)[A-Z0-9]{6,}$/;
49193
+ /** An IAM role / user ARN — the two shapes a unique id can stand in for. */
49194
+ const IAM_PRINCIPAL_ARN_RE = /^arn:[a-z0-9-]+:iam::\d{12}:(role|user)\/(.+)$/;
49195
+ /**
49196
+ * Split an IAM role / user principal ARN into what the lookup API needs, or
49197
+ * `undefined` when the string is not one (a service principal, an
49198
+ * `assumed-role` STS ARN, `*`, …).
49199
+ *
49200
+ * Exported so the resolver in `drift.ts` recognizes exactly the ARNs
49201
+ * {@link collectPrincipalArns} collects — one regex, so the collector and the
49202
+ * lookup cannot drift apart and leave an ARN gathered but never resolvable.
49203
+ */
49204
+ function parseIamPrincipalArn(arn) {
49205
+ const match = IAM_PRINCIPAL_ARN_RE.exec(arn);
49206
+ if (!match) return void 0;
49207
+ const name = match[2].split("/").pop();
49208
+ if (!name) return void 0;
49209
+ return {
49210
+ kind: match[1],
49211
+ name
49212
+ };
49213
+ }
49214
+ /** Every string at a `Principal` / `NotPrincipal` position, at any depth. */
49215
+ function collectPrincipalStrings(value, out) {
49216
+ if (Array.isArray(value)) {
49217
+ for (const element of value) collectPrincipalStrings(element, out);
49218
+ return;
49219
+ }
49220
+ if (!value || typeof value !== "object") return;
49221
+ for (const [key, member] of Object.entries(value)) {
49222
+ if (key === "Principal" || key === "NotPrincipal") pushPrincipalValue(member, out);
49223
+ collectPrincipalStrings(member, out);
49224
+ }
49225
+ }
49226
+ /**
49227
+ * The strings a `Principal` / `NotPrincipal` value contributes: the bare string
49228
+ * form, and the `AWS` member of the object form (string or array). `Service` /
49229
+ * `Federated` / `CanonicalUser` are deliberately NOT read — none of them can
49230
+ * carry an IAM unique id or a role ARN.
49231
+ */
49232
+ function pushPrincipalValue(value, out) {
49233
+ if (typeof value === "string") {
49234
+ out.push(value);
49235
+ return;
49236
+ }
49237
+ if (Array.isArray(value)) {
49238
+ for (const element of value) if (typeof element === "string") out.push(element);
49239
+ return;
49240
+ }
49241
+ if (!value || typeof value !== "object") return;
49242
+ const awsMember = value["AWS"];
49243
+ if (typeof awsMember === "string") out.push(awsMember);
49244
+ else if (Array.isArray(awsMember)) {
49245
+ for (const element of awsMember) if (typeof element === "string") out.push(element);
49246
+ }
49247
+ }
49248
+ /**
49249
+ * Both interesting principal kinds in ONE walk — the pass runs per resource on
49250
+ * both comparison sides, so walking the bag twice to answer two questions about
49251
+ * the same strings is pure cost.
49252
+ */
49253
+ function collectPrincipalForms(value) {
49254
+ const strings = [];
49255
+ collectPrincipalStrings(value, strings);
49256
+ const uniqueIds = /* @__PURE__ */ new Set();
49257
+ const arns = /* @__PURE__ */ new Set();
49258
+ for (const s of strings) if (PRINCIPAL_UNIQUE_ID_RE.test(s)) uniqueIds.add(s);
49259
+ else if (IAM_PRINCIPAL_ARN_RE.test(s)) arns.add(s);
49260
+ return {
49261
+ uniqueIds,
49262
+ arns
49263
+ };
49264
+ }
49265
+ /**
49266
+ * Replace every principal string that is a KNOWN unique id with the ARN it was
49267
+ * proven to stand for. Only `Principal` / `NotPrincipal` positions are touched
49268
+ * — a unique id appearing in, say, a `Condition` value is left alone, where the
49269
+ * two forms are NOT interchangeable.
49270
+ */
49271
+ function rewritePrincipalUniqueIds(value, arnByUniqueId) {
49272
+ if (Array.isArray(value)) return value.map((el) => rewritePrincipalUniqueIds(el, arnByUniqueId));
49273
+ if (!value || typeof value !== "object") return value;
49274
+ const out = {};
49275
+ for (const [key, member] of Object.entries(value)) out[key] = key === "Principal" || key === "NotPrincipal" ? rewritePrincipalValue(member, arnByUniqueId) : rewritePrincipalUniqueIds(member, arnByUniqueId);
49276
+ return out;
49277
+ }
49278
+ function rewritePrincipalValue(value, arnByUniqueId) {
49279
+ const swap = (s) => typeof s === "string" ? arnByUniqueId.get(s) ?? s : rewritePrincipalUniqueIds(s, arnByUniqueId);
49280
+ if (typeof value === "string") return swap(value);
49281
+ if (Array.isArray(value)) return value.map(swap);
49282
+ if (!value || typeof value !== "object") return value;
49283
+ const out = {};
49284
+ for (const [key, member] of Object.entries(value)) {
49285
+ if (key !== "AWS") {
49286
+ out[key] = rewritePrincipalUniqueIds(member, arnByUniqueId);
49287
+ continue;
49288
+ }
49289
+ out[key] = Array.isArray(member) ? member.map(swap) : swap(member);
49290
+ }
49291
+ return out;
49292
+ }
49293
+ /**
49294
+ * Canonicalize the two comparison sides so that a principal AWS rendered as a
49295
+ * unique id on one side and as an ARN on the other stops reading as drift.
49296
+ *
49297
+ * Short-circuits — with NO resolver call — unless BOTH a unique id and an IAM
49298
+ * ARN are present across the two sides, since a unique id with no candidate ARN
49299
+ * opposite it has nothing to be equal to.
49300
+ */
49301
+ async function canonicalizePrincipalUniqueIds(baseline, aws, resolve) {
49302
+ const baselineForms = collectPrincipalForms(baseline);
49303
+ const awsForms = collectPrincipalForms(aws);
49304
+ const uniqueIds = /* @__PURE__ */ new Set([...baselineForms.uniqueIds, ...awsForms.uniqueIds]);
49305
+ if (uniqueIds.size === 0) return {
49306
+ baseline,
49307
+ aws
49308
+ };
49309
+ const arns = /* @__PURE__ */ new Set([...baselineForms.arns, ...awsForms.arns]);
49310
+ if (arns.size === 0) return {
49311
+ baseline,
49312
+ aws
49313
+ };
49314
+ const arnByUniqueId = /* @__PURE__ */ new Map();
49315
+ for (const arn of arns) {
49316
+ const uniqueId = await resolve(arn);
49317
+ if (uniqueId !== void 0 && uniqueIds.has(uniqueId)) arnByUniqueId.set(uniqueId, arn);
49318
+ }
49319
+ if (arnByUniqueId.size === 0) return {
49320
+ baseline,
49321
+ aws
49322
+ };
49323
+ return {
49324
+ baseline: rewritePrincipalUniqueIds(baseline, arnByUniqueId),
49325
+ aws: rewritePrincipalUniqueIds(aws, arnByUniqueId)
49326
+ };
49327
+ }
49328
+
49072
49329
  //#endregion
49073
49330
  //#region src/analyzer/drift-cc-api-deny-list.ts
49074
49331
  /**
@@ -49283,11 +49540,12 @@ async function driftCommand(stacks, options) {
49283
49540
  registerAllProviders(providerRegistry);
49284
49541
  providerRegistry.setCustomResourceResponseBucket(bucket);
49285
49542
  const ccApiFallback = new CloudControlProvider();
49543
+ const resolvePrincipalUniqueId = createIamPrincipalUniqueIdResolver(awsClients);
49286
49544
  const targetRefs = resolveTargetRefs(stacks, await stateBackend.listStacks(), options);
49287
49545
  const reports = [];
49288
49546
  for (const ref of targetRefs) {
49289
49547
  if (!ref.region) throw new Error(`Stack '${ref.stackName}' has only a legacy state record without a region. Run 'cdkd deploy ${ref.stackName}' (or any cdkd write) to migrate it to the region-scoped layout, then re-run drift detection.`);
49290
- const report = await runDriftForStack(ref.stackName, ref.region, stateBackend, providerRegistry, ccApiFallback);
49548
+ const report = await runDriftForStack(ref.stackName, ref.region, stateBackend, providerRegistry, ccApiFallback, resolvePrincipalUniqueId);
49291
49549
  reports.push(report);
49292
49550
  }
49293
49551
  if (options.json) writeJsonReport(reports);
@@ -49349,13 +49607,75 @@ function resolveTargetRefs(stacks, stateRefs, options) {
49349
49607
  return out;
49350
49608
  }
49351
49609
  /**
49610
+ * The live half of the #1515 principal canonicalization: resolve an IAM role /
49611
+ * user ARN to that principal's unique id (`AROA…` / `AIDA…`).
49612
+ *
49613
+ * Results are cached per ARN for the whole command — INCLUDING the failures, so
49614
+ * a deleted role (the case where AWS keeps the unique id forever, and therefore
49615
+ * the likeliest one to appear here) costs exactly one API call rather than one
49616
+ * per resource that references it.
49617
+ *
49618
+ * Every failure resolves to `undefined` rather than throwing: a missing
49619
+ * `iam:GetRole` permission, a cross-account principal, or a deleted role must
49620
+ * leave the comparison exactly as it was — the drift is then reported, which is
49621
+ * the safe direction. Drift detection must never fail because a cosmetic
49622
+ * normalization could not run.
49623
+ *
49624
+ * **The response ARN is compared back to the requested one, and that check is
49625
+ * what makes the "never hides a real change" claim true rather than aspirational.**
49626
+ * `GetRole` / `GetUser` are account-local and take a NAME, not an ARN, so the
49627
+ * lookup silently answers about the CALLER's account and about the wrong IAM
49628
+ * path: `arn:aws:iam::<other-acct>:role/Foo` and
49629
+ * `arn:aws:iam::<self>:role/prod/Foo` would both come back as this account's
49630
+ * root-path `Foo`, and the pass would then "prove" two DIFFERENT principals
49631
+ * equal and collapse a genuine drift. Round-tripping the ARN costs nothing and
49632
+ * turns both cases into an unresolved lookup, i.e. drift reported.
49633
+ */
49634
+ function createIamPrincipalUniqueIdResolver(awsClients) {
49635
+ const cache = /* @__PURE__ */ new Map();
49636
+ let warnedOnDenied = false;
49637
+ return async (arn) => {
49638
+ if (cache.has(arn)) return cache.get(arn);
49639
+ let uniqueId;
49640
+ const principal = parseIamPrincipalArn(arn);
49641
+ if (principal) try {
49642
+ let entityArn;
49643
+ let entityId;
49644
+ if (principal.kind === "role") {
49645
+ const role = (await awsClients.iam.send(new GetRoleCommand({ RoleName: principal.name }))).Role;
49646
+ entityArn = role?.Arn;
49647
+ entityId = role?.RoleId;
49648
+ } else {
49649
+ const user = (await awsClients.iam.send(new GetUserCommand({ UserName: principal.name }))).User;
49650
+ entityArn = user?.Arn;
49651
+ entityId = user?.UserId;
49652
+ }
49653
+ if (entityArn !== void 0 && entityArn.toLowerCase() === arn.toLowerCase()) uniqueId = entityId;
49654
+ else getLogger().debug(`Principal ${arn} resolved to ${entityArn ?? "no entity"} — same name in this account or under another IAM path; leaving the policy principal comparison untouched.`);
49655
+ } catch (error) {
49656
+ const message = error instanceof Error ? error.message : String(error);
49657
+ const name = error instanceof Error ? error.name : "";
49658
+ const status = error?.$metadata?.httpStatusCode;
49659
+ const denied = /^AccessDenied|^NotAuthorized/i.test(name) || status === 403;
49660
+ if (!warnedOnDenied && denied) {
49661
+ warnedOnDenied = true;
49662
+ getLogger().warn(`Cannot read IAM principals (${name || "access denied"}). A resource policy whose principal AWS rendered as a unique id (AROA…/AIDA…) may therefore report drift that is only a spelling difference; grant iam:GetRole / iam:GetUser to resolve it, or re-run with --verbose for the full error.`);
49663
+ }
49664
+ getLogger().debug(`Could not resolve the unique id of principal ${arn} (${message}); leaving the policy principal comparison untouched.`);
49665
+ if (isThrottlingError(error)) return void 0;
49666
+ }
49667
+ cache.set(arn, uniqueId);
49668
+ return uniqueId;
49669
+ };
49670
+ }
49671
+ /**
49352
49672
  * Run drift detection for one stack and shape the per-resource outcomes
49353
49673
  * into a {@link StackDriftReport}. The state object + etag are stored on
49354
49674
  * the report so `--accept` can write back without a re-read, and
49355
49675
  * `--revert` can pass the captured AWS-current snapshot to
49356
49676
  * `provider.update` as the `previousProperties` argument.
49357
49677
  */
49358
- async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback) {
49678
+ async function runDriftForStack(stackName, region, stateBackend, providerRegistry, ccApiFallback, resolvePrincipalUniqueId) {
49359
49679
  const result = await stateBackend.getState(stackName, region);
49360
49680
  if (!result) throw new Error(`No state found for stack '${stackName}' (${region}). Run 'cdkd state list' to see available stacks.`);
49361
49681
  return await withStackName(stackName, async () => {
@@ -49421,7 +49741,8 @@ async function runDriftForStack(stackName, region, stateBackend, providerRegistr
49421
49741
  const useObserved = resource.observedProperties !== void 0;
49422
49742
  const baseline = useObserved ? resource.observedProperties : resource.properties ?? {};
49423
49743
  const observedIgnorePaths = useObserved ? undeclaredEmptyObservedKeys(resource.observedProperties, resource.properties ?? {}) : [];
49424
- const changes = calculateResourceDrift(baseline, aws, {
49744
+ const normalized = await canonicalizePrincipalUniqueIds(baseline, aws, resolvePrincipalUniqueId);
49745
+ const changes = calculateResourceDrift(normalized.baseline, normalized.aws, {
49425
49746
  ignorePaths: observedIgnorePaths.length ? [...ignorePaths, ...observedIgnorePaths] : ignorePaths,
49426
49747
  unionWalkObjects: useObserved,
49427
49748
  unorderedPaths
@@ -66897,7 +67218,7 @@ function createMigrateCommand() {
66897
67218
  */
66898
67219
  function buildProgram() {
66899
67220
  const program = new Command();
66900
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.37");
67221
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.278.39");
66901
67222
  program.hook("preAction", (_thisCommand, actionCommand) => {
66902
67223
  const { profile } = actionCommand.optsWithGlobals();
66903
67224
  if (profile !== void 0) process.env["AWS_PROFILE"] = profile;