@go-to-k/cdkd 0.281.17 → 0.281.19

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.
@@ -4176,11 +4176,70 @@ function cacheOptionToFlag(option) {
4176
4176
  return flag;
4177
4177
  }
4178
4178
 
4179
+ //#endregion
4180
+ //#region src/utils/aws-partition.ts
4181
+ /**
4182
+ * AWS partition / URL-suffix derivation, shared across layers.
4183
+ *
4184
+ * Lives in `src/utils/` because it has consumers in two different layers: the
4185
+ * `cdkd local *` command family (which passes a region in once to keep the STS
4186
+ * hop minimal) and the provisioning layer's `AppSyncProvider`, which rebuilds a
4187
+ * child resource's ARN when AWS did not report one.
4188
+ *
4189
+ * It was originally defined in `src/local/ecs-task-resolver.ts`, which
4190
+ * re-exports it so every existing call site is unchanged; a provisioning
4191
+ * provider importing from `src/local/**` would invert the layering.
4192
+ *
4193
+ * NOTE `getAccountInfo().partition`
4194
+ * (`src/deployment/intrinsic-function-resolver.ts`) was hardcoded to `'aws'`
4195
+ * until issue #1730, which made it derive through THIS helper — so the two now
4196
+ * agree and either spelling is correct. Prefer this one where a region is
4197
+ * already in hand, since it needs no STS round trip.
4198
+ */
4199
+ /**
4200
+ * Derive the AWS partition / URL suffix for an AWS region. Same mapping
4201
+ * CloudFormation applies to `${AWS::Partition}` / `${AWS::URLSuffix}`.
4202
+ */
4203
+ function derivePartitionAndUrlSuffix(region) {
4204
+ if (region.startsWith("cn-")) return {
4205
+ partition: "aws-cn",
4206
+ urlSuffix: "amazonaws.com.cn"
4207
+ };
4208
+ if (region.startsWith("us-gov-")) return {
4209
+ partition: "aws-us-gov",
4210
+ urlSuffix: "amazonaws.com"
4211
+ };
4212
+ if (region.startsWith("us-iso-")) return {
4213
+ partition: "aws-iso",
4214
+ urlSuffix: "c2s.ic.gov"
4215
+ };
4216
+ if (region.startsWith("us-isob-")) return {
4217
+ partition: "aws-iso-b",
4218
+ urlSuffix: "sc2s.sgov.gov"
4219
+ };
4220
+ return {
4221
+ partition: "aws",
4222
+ urlSuffix: "amazonaws.com"
4223
+ };
4224
+ }
4225
+
4179
4226
  //#endregion
4180
4227
  //#region src/assets/docker-asset-publisher.ts
4181
4228
  /**
4229
+ * The ECR registry host suffix for a region (issue #1745).
4230
+ *
4231
+ * Every registry URI here was hardcoded to `amazonaws.com`, so outside the
4232
+ * commercial partition cdkd built a hostname that does not resolve —
4233
+ * `aws-cn` registries live under `amazonaws.com.cn`, and `us-iso*` under
4234
+ * `c2s.ic.gov` / `sc2s.sgov.gov`. Commercial output is byte-identical, which is
4235
+ * what makes the change safe to ship without a non-commercial account.
4236
+ */
4237
+ function ecrUrlSuffix(region) {
4238
+ return derivePartitionAndUrlSuffix(region).urlSuffix;
4239
+ }
4240
+ /**
4182
4241
  * Registries this process has already logged in to, keyed by registry host
4183
- * (`<accountId>.dkr.ecr.<region>.amazonaws.com`, which uniquely encodes the
4242
+ * (`<accountId>.dkr.ecr.<region>.<urlSuffix>`, which uniquely encodes the
4184
4243
  * account + region credential context). ECR authorization tokens are valid
4185
4244
  * for ~12h and a deploy process is short-lived, so a successful login is
4186
4245
  * reused for the process lifetime — mirroring `cdk-assets`, which skips
@@ -4221,7 +4280,7 @@ var DockerAssetPublisher = class {
4221
4280
  const repositoryName = this.resolvePlaceholders(dest.repositoryName, accountId, region);
4222
4281
  const imageTag = this.resolvePlaceholders(dest.imageTag, accountId, region);
4223
4282
  const destRegion = dest.region ? this.resolvePlaceholders(dest.region, accountId, region) : region;
4224
- const ecrUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;
4283
+ const ecrUri = `${accountId}.dkr.ecr.${destRegion}.${ecrUrlSuffix(destRegion)}/${repositoryName}:${imageTag}`;
4225
4284
  this.logger.debug(`Publishing Docker image ${asset.displayName || assetHash} → ${ecrUri}`);
4226
4285
  const client = new ECRClient({ region: destRegion });
4227
4286
  try {
@@ -4231,8 +4290,7 @@ var DockerAssetPublisher = class {
4231
4290
  }
4232
4291
  const localTag = `cdkd-asset-${assetHash}`;
4233
4292
  await this.buildImage(asset, cdkOutputDir, localTag);
4234
- const fullUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;
4235
- await this.tagAndPushWithLazyLogin(client, localTag, fullUri, accountId, destRegion);
4293
+ await this.tagAndPushWithLazyLogin(client, localTag, ecrUri, accountId, destRegion);
4236
4294
  this.logger.debug(`✅ Published: ${ecrUri}`);
4237
4295
  } finally {
4238
4296
  client.destroy();
@@ -4259,15 +4317,14 @@ var DockerAssetPublisher = class {
4259
4317
  const repositoryName = this.resolvePlaceholders(dest.repositoryName, accountId, region);
4260
4318
  const imageTag = this.resolvePlaceholders(dest.imageTag, accountId, region);
4261
4319
  const destRegion = dest.region ? this.resolvePlaceholders(dest.region, accountId, region) : region;
4262
- const ecrUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;
4320
+ const ecrUri = `${accountId}.dkr.ecr.${destRegion}.${ecrUrlSuffix(destRegion)}/${repositoryName}:${imageTag}`;
4263
4321
  const client = new ECRClient({ region: destRegion });
4264
4322
  try {
4265
4323
  if (await this.imageExists(client, repositoryName, imageTag)) {
4266
4324
  this.logger.debug(`Image already exists, skipping: ${ecrUri}`);
4267
4325
  continue;
4268
4326
  }
4269
- const fullUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;
4270
- await this.tagAndPushWithLazyLogin(client, localTag, fullUri, accountId, destRegion);
4327
+ await this.tagAndPushWithLazyLogin(client, localTag, ecrUri, accountId, destRegion);
4271
4328
  this.logger.debug(`✅ Published: ${ecrUri}`);
4272
4329
  } finally {
4273
4330
  client.destroy();
@@ -4354,7 +4411,7 @@ var DockerAssetPublisher = class {
4354
4411
  /**
4355
4412
  * Authenticate with ECR via `docker login --password-stdin`.
4356
4413
  *
4357
- * The login is cached per registry (`<accountId>.dkr.ecr.<region>`) for the
4414
+ * The login is cached per registry (`<accountId>.dkr.ecr.<region>.<urlSuffix>`) for the
4358
4415
  * process lifetime: a repeat publish to the same registry returns early
4359
4416
  * without the `GetAuthorizationToken` call or the `docker login` subprocess
4360
4417
  * (mirrors `cdk-assets`). ECR tokens are valid ~12h and a deploy process is
@@ -4366,7 +4423,7 @@ var DockerAssetPublisher = class {
4366
4423
  * token that AWS has already expired.
4367
4424
  */
4368
4425
  async ecrLogin(client, accountId, region, options = {}) {
4369
- const registryKey = `${accountId}.dkr.ecr.${region}.amazonaws.com`;
4426
+ const registryKey = `${accountId}.dkr.ecr.${region}.${ecrUrlSuffix(region)}`;
4370
4427
  if (!options.force && loggedInRegistries.has(registryKey)) {
4371
4428
  this.logger.debug(`Reusing cached ECR login for ${registryKey}`);
4372
4429
  return;
@@ -4375,7 +4432,7 @@ var DockerAssetPublisher = class {
4375
4432
  if (!authData?.authorizationToken) throw new AssetError("Failed to get ECR authorization token");
4376
4433
  const [username, password] = Buffer.from(authData.authorizationToken, "base64").toString().split(":");
4377
4434
  if (!username || password === void 0) throw new AssetError("ECR authorization token has unexpected shape (missing username/password)");
4378
- const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.amazonaws.com`;
4435
+ const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.${ecrUrlSuffix(region)}`;
4379
4436
  try {
4380
4437
  await runDockerStreaming([
4381
4438
  "login",
@@ -4995,8 +5052,8 @@ function evaluatePseudoParam(name, map) {
4995
5052
  switch (name) {
4996
5053
  case "AWS::AccountId": return map.accountId;
4997
5054
  case "AWS::Region": return map.region;
4998
- case "AWS::Partition": return map.partition;
4999
- case "AWS::URLSuffix": return "amazonaws.com";
5055
+ case "AWS::Partition": return derivePartitionAndUrlSuffix(map.region).partition;
5056
+ case "AWS::URLSuffix": return derivePartitionAndUrlSuffix(map.region).urlSuffix;
5000
5057
  default: throw new Error(`Not a foldable pseudo parameter: ${name}`);
5001
5058
  }
5002
5059
  }
@@ -9232,53 +9289,6 @@ async function applyRoleArnIfSet(opts) {
9232
9289
  }
9233
9290
  }
9234
9291
 
9235
- //#endregion
9236
- //#region src/utils/aws-partition.ts
9237
- /**
9238
- * AWS partition / URL-suffix derivation, shared across layers.
9239
- *
9240
- * Lives in `src/utils/` because it has consumers in two different layers: the
9241
- * `cdkd local *` command family (which passes a region in once to keep the STS
9242
- * hop minimal) and the provisioning layer's `AppSyncProvider`, which rebuilds a
9243
- * child resource's ARN when AWS did not report one.
9244
- *
9245
- * It was originally defined in `src/local/ecs-task-resolver.ts`, which
9246
- * re-exports it so every existing call site is unchanged; a provisioning
9247
- * provider importing from `src/local/**` would invert the layering.
9248
- *
9249
- * NOTE `getAccountInfo().partition`
9250
- * (`src/deployment/intrinsic-function-resolver.ts`) was hardcoded to `'aws'`
9251
- * until issue #1730, which made it derive through THIS helper — so the two now
9252
- * agree and either spelling is correct. Prefer this one where a region is
9253
- * already in hand, since it needs no STS round trip.
9254
- */
9255
- /**
9256
- * Derive the AWS partition / URL suffix for an AWS region. Same mapping
9257
- * CloudFormation applies to `${AWS::Partition}` / `${AWS::URLSuffix}`.
9258
- */
9259
- function derivePartitionAndUrlSuffix(region) {
9260
- if (region.startsWith("cn-")) return {
9261
- partition: "aws-cn",
9262
- urlSuffix: "amazonaws.com.cn"
9263
- };
9264
- if (region.startsWith("us-gov-")) return {
9265
- partition: "aws-us-gov",
9266
- urlSuffix: "amazonaws.com"
9267
- };
9268
- if (region.startsWith("us-iso-")) return {
9269
- partition: "aws-iso",
9270
- urlSuffix: "c2s.ic.gov"
9271
- };
9272
- if (region.startsWith("us-isob-")) return {
9273
- partition: "aws-iso-b",
9274
- urlSuffix: "sc2s.sgov.gov"
9275
- };
9276
- return {
9277
- partition: "aws",
9278
- urlSuffix: "amazonaws.com"
9279
- };
9280
- }
9281
-
9282
9292
  //#endregion
9283
9293
  //#region src/provisioning/config-shape.ts
9284
9294
  /**
@@ -9561,6 +9571,63 @@ function configStringRefusal(container, key, fallback, containerPath, options) {
9561
9571
  if (!isPlainObject$1(container)) return `${containerPath} must be an object ${malformedShapeDetail(container)}`;
9562
9572
  return configValueRefusal(container[key], fallback, `${containerPath}.${key}`, options);
9563
9573
  }
9574
+ /**
9575
+ * Coerce a CFn boolean, which may arrive as the string `"true"` / `"false"`.
9576
+ *
9577
+ * CloudFormation is stringly typed and cdkd is not, so a hand-written or
9578
+ * imported template legitimately spells a boolean as a string. Case-insensitive
9579
+ * on purpose: CDK renders lowercase, but this also feeds
9580
+ * `AWS::S3::Bucket NotificationConfiguration.EventBridgeConfiguration` (issue
9581
+ * #1430), where "not false" means "enable" — so a `'False'` that fell through to
9582
+ * `undefined` would silently ENABLE EventBridge delivery, the exact inversion
9583
+ * #1430 fixed.
9584
+ *
9585
+ * Lives here rather than in the one provider that used to own it because
9586
+ * {@link configBooleanRefusal} must run the SAME primitive the wire read runs;
9587
+ * a second hand-written test would disagree with it on exactly the interesting
9588
+ * values, which is the guard-mismatch shape this module exists to stop.
9589
+ *
9590
+ * @returns the boolean, or `undefined` when the value is not one.
9591
+ */
9592
+ function coerceCfnBoolean(value) {
9593
+ if (typeof value === "boolean") return value;
9594
+ if (typeof value === "string") {
9595
+ const lowered = value.toLowerCase();
9596
+ if (lowered === "true") return true;
9597
+ if (lowered === "false") return false;
9598
+ }
9599
+ }
9600
+ /**
9601
+ * The BOOLEAN twin of {@link configStringRefusal} — the refusal SENTENCE for a
9602
+ * config member that is read as a boolean, with no action clause attached.
9603
+ *
9604
+ * Same two halves in the same order (CONTAINER, then FIELD) and the same
9605
+ * shared detail clause, so a boolean guard and a string guard on sibling
9606
+ * members of one block cannot word the same fault differently. The FIELD half
9607
+ * is `coerceCfnBoolean`, i.e. literally the function the wire read calls, per
9608
+ * this module's "share the predicate, never restate it" rule.
9609
+ *
9610
+ * It exists because a boolean member read as `x ?? <default>` is the one shape
9611
+ * neither string guard can see: `??` treats a DECLARED `null` as absent and
9612
+ * substitutes the default, which for `AWS::S3::Bucket
9613
+ * InventoryConfigurations[].Enabled` meant a declared `Enabled: null` went on
9614
+ * the wire as `true` — a report the template may have been disabling, ENABLED
9615
+ * with no warning anywhere (issue #1751). Its string siblings on the same item
9616
+ * are SKIP-guarded (#1595) or warn-and-substitute (#1670); this was the one
9617
+ * member that silently coerced.
9618
+ *
9619
+ * @returns The refusal sentence (`<path> must be …`), or `undefined` when the
9620
+ * value is usable — including the ABSENT container / ABSENT key cases, which
9621
+ * legitimately take the caller's default.
9622
+ */
9623
+ function configBooleanRefusal(container, key, containerPath) {
9624
+ if (container === void 0 || container === null) return void 0;
9625
+ if (!isPlainObject$1(container)) return `${containerPath} must be an object ${malformedShapeDetail(container)}`;
9626
+ const value = container[key];
9627
+ if (value === void 0) return void 0;
9628
+ if (coerceCfnBoolean(value) !== void 0) return void 0;
9629
+ return `${containerPath}.${key} must be a boolean ${malformedShapeDetail(value)}`;
9630
+ }
9564
9631
  /** The FIELD half of {@link configStringRefusal}, shared with {@link requireConfigString}. */
9565
9632
  function configValueRefusal(value, fallback, path, options) {
9566
9633
  if (value === void 0) return void 0;
@@ -10767,7 +10834,7 @@ function buildUnknownIntrinsicError(key) {
10767
10834
  const issueUrl = `https://github.com/go-to-k/cdkd/issues/new?title=${encodeURIComponent(title)}&labels=intrinsic-support`;
10768
10835
  return /* @__PURE__ */ new Error(`Unsupported CloudFormation intrinsic function "${key}": cdkd does not support resolving it yet. Deploying this template would produce a broken value. Please request support by opening an issue: ${issueUrl}`);
10769
10836
  }
10770
- let cachedAccountInfo = null;
10837
+ let cachedAccountIdentity = null;
10771
10838
  /**
10772
10839
  * Cache for availability zones per region
10773
10840
  */
@@ -10786,18 +10853,30 @@ const cachedDynamicReferences = {};
10786
10853
  */
10787
10854
  const cachedEc2InstanceAttributes = {};
10788
10855
  /**
10789
- * Re-derive the partition for a region the caller overrode (issue #1730).
10856
+ * The region this call answers for: the caller's override, else the ambient one.
10857
+ *
10858
+ * Kept as one helper so the cached and the freshly-resolved paths cannot pick
10859
+ * different defaults (issue #1746).
10860
+ */
10861
+ function effectiveAccountInfoRegion(overrideRegion) {
10862
+ return overrideRegion || process.env["AWS_REGION"] || "us-east-1";
10863
+ }
10864
+ /**
10865
+ * Build the caller's full answer from the cached account identity (issue #1746).
10790
10866
  *
10791
- * `partition` is a FUNCTION of `region`, so handing back a cached entry with a
10792
- * different region than the one its partition was derived from would produce
10793
- * `arn:aws:...:cn-north-1:...` for a `cn-` override. Every return path that
10794
- * swaps the region goes through here.
10867
+ * `partition` is a FUNCTION of `region`, so it is derived HERE per call —
10868
+ * rather than carried alongside the account. This is what makes the cache safe
10869
+ * to share between callers with different regions: an `arn:aws:...:cn-north-1`
10870
+ * (or the inverse `arn:aws-cn:...:us-east-1`) is structurally valid, so nothing
10871
+ * downstream could catch it.
10795
10872
  */
10796
- function withOverrideRegion(info, region) {
10873
+ function accountInfoFor(identity, overrideRegion) {
10874
+ const region = effectiveAccountInfoRegion(overrideRegion);
10797
10875
  return {
10798
- ...info,
10876
+ accountId: identity.accountId,
10799
10877
  region,
10800
- partition: derivePartitionAndUrlSuffix(region).partition
10878
+ partition: derivePartitionAndUrlSuffix(region).partition,
10879
+ ...identity.fabricated ? { fabricated: true } : {}
10801
10880
  };
10802
10881
  }
10803
10882
  /**
@@ -10814,7 +10893,7 @@ function withOverrideRegion(info, region) {
10814
10893
  const FABRICATED_ACCOUNT_INFO_TTL_MS = 1e4;
10815
10894
  /** Test seam for {@link FABRICATED_ACCOUNT_INFO_TTL_MS} expiry. */
10816
10895
  const accountInfoClock = { now: () => Date.now() };
10817
- let fabricatedAccountInfo = null;
10896
+ let fabricatedAccountIdentity = null;
10818
10897
  /**
10819
10898
  * The single in-flight lookup, so N concurrent callers share ONE round trip.
10820
10899
  *
@@ -10826,66 +10905,95 @@ let fabricatedAccountInfo = null;
10826
10905
  */
10827
10906
  let accountInfoInFlight = null;
10828
10907
  /**
10908
+ * Bumped by {@link resetAccountInfoCache}, so a lookup that was already in
10909
+ * flight cannot write the cache it was asked to forget.
10910
+ *
10911
+ * Without it the reset only cleared the SETTLED caches: an in-flight resolve
10912
+ * would land afterwards and re-populate `cachedAccountIdentity`, so the next
10913
+ * caller read the pre-reset account. That is the `*Once`-leak shape one layer
10914
+ * down — a later test silently inheriting an earlier one's answer — and the
10915
+ * reset's own comment already claimed to forget it.
10916
+ */
10917
+ let accountInfoGeneration = 0;
10918
+ /**
10829
10919
  * Get AWS account information from STS
10830
10920
  */
10831
10921
  async function getAccountInfo(overrideRegion) {
10832
- const forRegion = (info) => overrideRegion && overrideRegion !== info.region ? withOverrideRegion(info, overrideRegion) : info;
10833
- if (cachedAccountInfo) return forRegion(cachedAccountInfo);
10834
- if (fabricatedAccountInfo && accountInfoClock.now() < fabricatedAccountInfo.expiresAt) return forRegion(fabricatedAccountInfo.info);
10835
- if (accountInfoInFlight) return forRegion(await accountInfoInFlight);
10836
- accountInfoInFlight = resolveAccountInfo(overrideRegion);
10922
+ if (cachedAccountIdentity) return accountInfoFor(cachedAccountIdentity, overrideRegion);
10923
+ if (fabricatedAccountIdentity && accountInfoClock.now() < fabricatedAccountIdentity.expiresAt) return accountInfoFor(fabricatedAccountIdentity.identity, overrideRegion);
10924
+ if (accountInfoInFlight) return accountInfoFor(await accountInfoInFlight, overrideRegion);
10925
+ const inFlight = resolveAccountIdentity();
10926
+ accountInfoInFlight = inFlight;
10837
10927
  try {
10838
- return forRegion(await accountInfoInFlight);
10928
+ return accountInfoFor(await inFlight, overrideRegion);
10839
10929
  } finally {
10840
- accountInfoInFlight = null;
10930
+ if (accountInfoInFlight === inFlight) accountInfoInFlight = null;
10841
10931
  }
10842
10932
  }
10843
- async function resolveAccountInfo(overrideRegion) {
10933
+ async function resolveAccountIdentity() {
10934
+ const generation = accountInfoGeneration;
10935
+ const stillCurrent = () => generation === accountInfoGeneration;
10844
10936
  const logger = getLogger().child("IntrinsicFunctionResolver");
10845
10937
  const stsClient = getAwsClients().sts;
10846
10938
  try {
10847
10939
  const response = await stsClient.send(new GetCallerIdentityCommand({}));
10848
10940
  const accountId = response.Account || "123456789012";
10849
- const region = overrideRegion || process.env["AWS_REGION"] || "us-east-1";
10850
- const partition = derivePartitionAndUrlSuffix(region).partition;
10851
10941
  const resolved = {
10852
10942
  accountId,
10853
- region,
10854
- partition,
10855
10943
  ...response.Account ? {} : { fabricated: true }
10856
10944
  };
10857
- if (resolved.fabricated) fabricatedAccountInfo = {
10858
- info: resolved,
10945
+ if (!stillCurrent()) {} else if (resolved.fabricated) fabricatedAccountIdentity = {
10946
+ identity: resolved,
10859
10947
  expiresAt: accountInfoClock.now() + FABRICATED_ACCOUNT_INFO_TTL_MS
10860
10948
  };
10861
10949
  else {
10862
- cachedAccountInfo = resolved;
10863
- fabricatedAccountInfo = null;
10950
+ cachedAccountIdentity = resolved;
10951
+ fabricatedAccountIdentity = null;
10864
10952
  }
10865
- logger.debug(`Retrieved AWS account info: ${accountId}, ${region}, ${partition}`);
10953
+ logger.debug(`Retrieved AWS account info: ${accountId}`);
10866
10954
  return resolved;
10867
10955
  } catch (error) {
10868
10956
  logger.warn(`Failed to get AWS account info from STS: ${error instanceof Error ? error.message : String(error)}, using defaults`);
10869
- const region = overrideRegion || process.env["AWS_REGION"] || "us-east-1";
10870
10957
  const fallback = {
10871
10958
  accountId: process.env["AWS_ACCOUNT_ID"] || "123456789012",
10872
- region,
10873
- partition: derivePartitionAndUrlSuffix(region).partition,
10874
10959
  ...process.env["AWS_ACCOUNT_ID"] ? {} : { fabricated: true }
10875
10960
  };
10961
+ if (!stillCurrent()) return fallback;
10876
10962
  if (fallback.fabricated) {
10877
- if (!cachedAccountInfo) fabricatedAccountInfo = {
10878
- info: fallback,
10963
+ if (!cachedAccountIdentity) fabricatedAccountIdentity = {
10964
+ identity: fallback,
10879
10965
  expiresAt: accountInfoClock.now() + FABRICATED_ACCOUNT_INFO_TTL_MS
10880
10966
  };
10881
10967
  } else {
10882
- cachedAccountInfo = fallback;
10883
- fabricatedAccountInfo = null;
10968
+ cachedAccountIdentity = fallback;
10969
+ fabricatedAccountIdentity = null;
10884
10970
  }
10885
10971
  return fallback;
10886
10972
  }
10887
10973
  }
10888
10974
  /**
10975
+ * Does a constructed `Fn::GetAtt` answer embed the placeholder account id?
10976
+ *
10977
+ * The guard in `constructGuardedAttribute` used to test
10978
+ * `typeof value === 'string'` directly (issue #1746). Every account-bearing
10979
+ * branch of `constructAttribute` returns a string today — the only non-string
10980
+ * returns are the EC2 IPv6 CIDR LISTS, which carry no account — so that was
10981
+ * complete as written, but a future list-valued attribute embedding an account
10982
+ * would have slipped past silently with no test failing. Walking string arrays
10983
+ * (one level, which is the shape `constructAttribute` actually produces) closes
10984
+ * it now rather than at the moment someone adds one. A non-string, non-array
10985
+ * value is not account-bearing by construction and is left alone.
10986
+ *
10987
+ * EXPORTED for its own test: no `constructAttribute` branch returns an
10988
+ * account-bearing array today, so the array arm is unreachable through the
10989
+ * public resolver API and would ship unexercised otherwise.
10990
+ */
10991
+ function embedsAccountId(value, accountId) {
10992
+ if (typeof value === "string") return value.includes(accountId);
10993
+ if (Array.isArray(value)) return value.some((entry) => typeof entry === "string" && entry.includes(accountId));
10994
+ return false;
10995
+ }
10996
+ /**
10889
10997
  * Collect every name referenced (Ref / Fn::Sub placeholder / other intrinsic
10890
10998
  * argument) by the sections cdkd actually evaluates: Resources, Outputs, and
10891
10999
  * Conditions. Deliberately excludes `Rules` (assertion-only, never evaluated
@@ -11349,7 +11457,7 @@ var IntrinsicFunctionResolver = class {
11349
11457
  async constructGuardedAttribute(resource, attributeName, context, logicalId) {
11350
11458
  const accountInfo = await getAccountInfo(this.resolverRegion);
11351
11459
  const value = await this.constructAttribute(resource, attributeName, context, logicalId, accountInfo);
11352
- if (accountInfo.fabricated && typeof value === "string" && value.includes(accountInfo.accountId)) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resource.resourceType}: STS did not report this deploy's account id, so cdkd would build the value from the placeholder account ${accountInfo.accountId} — structurally valid, naming a different account, and indistinguishable downstream from a real one. Fix the AWS credentials (or set AWS_ACCOUNT_ID to this deploy's account) and deploy again.`);
11460
+ if (accountInfo.fabricated && embedsAccountId(value, accountInfo.accountId)) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resource.resourceType}: STS did not report this deploy's account id, so cdkd would build the value from the placeholder account ${accountInfo.accountId} — structurally valid, naming a different account, and indistinguishable downstream from a real one. Fix the AWS credentials (or set AWS_ACCOUNT_ID to this deploy's account) and deploy again.`);
11353
11461
  return value;
11354
11462
  }
11355
11463
  /**
@@ -13404,7 +13512,7 @@ var CloudControlProvider = class {
13404
13512
  if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
13405
13513
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
13406
13514
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
13407
- const { ASGProvider } = await import("./asg-provider-MpqWnvQI.js").then((n) => n.n);
13515
+ const { ASGProvider } = await import("./asg-provider-CE-lwlgZ.js").then((n) => n.n);
13408
13516
  await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
13409
13517
  return;
13410
13518
  }
@@ -13554,7 +13662,7 @@ var CloudControlProvider = class {
13554
13662
  * else's account, and the record heals on the resource's next update.
13555
13663
  */
13556
13664
  async accountInfoForSynthesizedArn(resourceType, attributeName, physicalId) {
13557
- const accountInfo = await getAccountInfo();
13665
+ const accountInfo = await getAccountInfo(await this.cloudControlClient.config.region());
13558
13666
  if (accountInfo.fabricated) {
13559
13667
  this.logger.warn(`Not enriching ${resourceType} ${attributeName} for ${physicalId}: STS did not report this deploy's account id, so the value would be built from a placeholder account and would be indistinguishable from a real one. Fix the credentials (or set AWS_ACCOUNT_ID) and deploy again — the record heals on the next update.`);
13560
13668
  return;
@@ -20312,7 +20420,7 @@ const FLUSH_INTERVAL_MS = 2e3;
20312
20420
  const FLUSH_EVENT_THRESHOLD = 50;
20313
20421
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
20314
20422
  function getCdkdVersion() {
20315
- return "0.281.17";
20423
+ return "0.281.19";
20316
20424
  }
20317
20425
  /**
20318
20426
  * Generate a time-sortable unique run id, e.g.
@@ -22480,5 +22588,5 @@ var DeployEngine = class {
22480
22588
  };
22481
22589
 
22482
22590
  //#endregion
22483
- export { replayWarn as $, findLargeInlineResources as $t, green as A, isCdkdError as An, buildDockerImage as At, slowCcOperationTimeoutMs as B, getLegacyStateBucketName as Bt, MULTI_REGION_RECREATE_BLOCKED_TYPES as C, ResourceTimeoutError as Cn, AssetModeResolver as Ct, bold as D, StateError as Dn, parseBootstrapMarker as Dt, formatResourceLine as E, StackTerminationProtectionError as En, getBootstrapMarkerKey as Et, clearOnUpdateRemoval as F, AssetManifestLoader as Ft, getAccountInfo as G, resolveStateBucketWithDefault as Gt, isTerminationProtectionPropagationError as H, resolveAutoAssetStorage as Ht, ProviderRegistry as I, getDockerImageBySourceHash as It, normalizeAwsTagsToCfn as J, stateBucketExistenceConfirmed as Jt, refStateLookupFromResource as K, resolveStateBucketWithDefaultAndSource as Kt, findActionableSilentDrops as L, Synthesizer as Lt, yellow as M, withErrorHandling as Mn, getDockerCmd as Mt, IAMRoleProvider as N, __exportAll as Nn, runDockerForeground as Nt, cyan as O, SynthesisError as On, validateAssetBucketName as Ot, collectInlinePolicyNamesManagedBySiblings as P, runDockerStreaming as Pt, readConfigString as Q, MIGRATE_TMP_PREFIX as Qt, findSilentDropProperties as R, synthesisStatusMessage as Rt, extractDeploymentEventError as S, ProvisioningError as Sn, rewriteTemplateAssetReferences as St, renderStatefulReason as T, StackHasActiveImportsError as Tn, ensureAssetStorage as Tt, IntrinsicFunctionResolver as U, resolveCaptureObservedState as Ut, disableInstanceApiTermination as V, resolveApp as Vt, cfnRefValueFromPhysicalId as W, resolveSkipPrefix as Wt, assertRegionMatch as X, CFN_TEMPLATE_BODY_LIMIT as Xt, resolveExplicitPhysicalId as Y, warnDeprecatedNoPrefixCliFlag as Yt, configStringRefusal as Z, CFN_TEMPLATE_URL_LIMIT as Zt, createPreDeleteFinalSnapshot as _, LocalStartServiceError as _n, stringifyValue as _t, DeploymentEventsStore as a, resolveBucketRegion as an, DiffCalculator as at, unsupportedFinalSnapshotError as b, NestedStackChildDirectDestroyError as bn, createAssetRedirectResolver as bt, replayFailedOperations as c, resetAwsClients as cn, isRetryableTransientError as ct, IMPLICIT_DELETE_DEPENDENCIES as d, CdkdError as dn, TemplateParser as dt, uploadCfnTemplate as en, requireConfigArray as et, computeImplicitDeleteEdges as f, ConfigError as fn, LockManager as ft, ccRoutedFinalSnapshotError as g, LocalMigrateError as gn, AssetPublisher as gt, buildFinalSnapshotIdentifier as h, LocalInvokeBuildError as hn, shouldRetainResource as ht, DeploymentEventsReader as i, clearBucketRegionCache as in, applyRoleArnIfSet as it, red as j, normalizeAwsError as jn, formatDockerLoginError as jt, gray as k, formatError as kn, validateContainerRepoName as kt, replayRollback as l, setAwsClients as ln, isThrottlingError as lt, PRE_DELETE_SNAPSHOT_TYPES as m, DeployCancelledError as mn, rebuildClientForBucketRegion as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, AssemblyReader as nn, requireConfigString as nt, planFailedOps as o, AwsClients as on, describeTypeWithThrottleRetry as ot, ATOMIC_FINAL_SNAPSHOT_TYPES as p, DependencyError as pn, S3StateBackend as pt, WAFv2WebACLProvider as q, resolveUseCdkBootstrapAssets as qt, DeployEngine as r, processStackMessages as rn, derivePartitionAndUrlSuffix as rt, planRollback as s, getAwsClients as sn, withRetry as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, expectedOwnerParam as tn, requireConfigObject as tt, withResourceDeadline as u, AssetError as un, DagBuilder as ut, isFinalSnapshotError as v, LockError as vn, WorkGraph as vt, isStatefulRecreateTargetSync as w, ResourceUpdateNotSupportedError as wn, BOOTSTRAP_MARKER_PREFIX as wt, makeCanonicalizePropertiesFn as x, PartialFailureError as xn, loadPublishableAssetManifest as xt, refusesFinalSnapshot as y, MissingCdkCliError as yn, buildAssetRedirectMap as yt, CloudControlProvider as z, getDefaultStateBucketName as zt };
22484
- //# sourceMappingURL=deploy-engine-B6DcyQpS.js.map
22591
+ export { configStringRefusal as $, CFN_TEMPLATE_URL_LIMIT as $t, green as A, SynthesisError as An, validateContainerRepoName as At, slowCcOperationTimeoutMs as B, synthesisStatusMessage as Bt, MULTI_REGION_RECREATE_BLOCKED_TYPES as C, PartialFailureError as Cn, rewriteTemplateAssetReferences as Ct, bold as D, StackHasActiveImportsError as Dn, getBootstrapMarkerKey as Dt, formatResourceLine as E, ResourceUpdateNotSupportedError as En, ensureAssetStorage as Et, clearOnUpdateRemoval as F, __exportAll as Fn, runDockerForeground as Ft, getAccountInfo as G, resolveCaptureObservedState as Gt, isTerminationProtectionPropagationError as H, getLegacyStateBucketName as Ht, ProviderRegistry as I, runDockerStreaming as It, normalizeAwsTagsToCfn as J, resolveStateBucketWithDefaultAndSource as Jt, refStateLookupFromResource as K, resolveSkipPrefix as Kt, findActionableSilentDrops as L, AssetManifestLoader as Lt, yellow as M, isCdkdError as Mn, buildDockerImage as Mt, IAMRoleProvider as N, normalizeAwsError as Nn, formatDockerLoginError as Nt, cyan as O, StackTerminationProtectionError as On, parseBootstrapMarker as Ot, collectInlinePolicyNamesManagedBySiblings as P, withErrorHandling as Pn, getDockerCmd as Pt, configBooleanRefusal as Q, CFN_TEMPLATE_BODY_LIMIT as Qt, findSilentDropProperties as R, getDockerImageBySourceHash as Rt, extractDeploymentEventError as S, NestedStackChildDirectDestroyError as Sn, loadPublishableAssetManifest as St, renderStatefulReason as T, ResourceTimeoutError as Tn, BOOTSTRAP_MARKER_PREFIX as Tt, IntrinsicFunctionResolver as U, resolveApp as Ut, disableInstanceApiTermination as V, getDefaultStateBucketName as Vt, cfnRefValueFromPhysicalId as W, resolveAutoAssetStorage as Wt, assertRegionMatch as X, stateBucketExistenceConfirmed as Xt, resolveExplicitPhysicalId as Y, resolveUseCdkBootstrapAssets as Yt, coerceCfnBoolean as Z, warnDeprecatedNoPrefixCliFlag as Zt, createPreDeleteFinalSnapshot as _, LocalInvokeBuildError as _n, AssetPublisher as _t, DeploymentEventsStore as a, processStackMessages as an, applyRoleArnIfSet as at, unsupportedFinalSnapshotError as b, LockError as bn, buildAssetRedirectMap as bt, replayFailedOperations as c, AwsClients as cn, withRetry as ct, IMPLICIT_DELETE_DEPENDENCIES as d, setAwsClients as dn, DagBuilder as dt, MIGRATE_TMP_PREFIX as en, readConfigString as et, computeImplicitDeleteEdges as f, AssetError as fn, TemplateParser as ft, ccRoutedFinalSnapshotError as g, DeployCancelledError as gn, shouldRetainResource as gt, buildFinalSnapshotIdentifier as h, DependencyError as hn, rebuildClientForBucketRegion as ht, DeploymentEventsReader as i, AssemblyReader as in, requireConfigString as it, red as j, formatError as jn, derivePartitionAndUrlSuffix as jt, gray as k, StateError as kn, validateAssetBucketName as kt, replayRollback as l, getAwsClients as ln, isRetryableTransientError as lt, PRE_DELETE_SNAPSHOT_TYPES as m, ConfigError as mn, S3StateBackend as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, uploadCfnTemplate as nn, requireConfigArray as nt, planFailedOps as o, clearBucketRegionCache as on, DiffCalculator as ot, ATOMIC_FINAL_SNAPSHOT_TYPES as p, CdkdError as pn, LockManager as pt, WAFv2WebACLProvider as q, resolveStateBucketWithDefault as qt, DeployEngine as r, expectedOwnerParam as rn, requireConfigObject as rt, planRollback as s, resolveBucketRegion as sn, describeTypeWithThrottleRetry as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, findLargeInlineResources as tn, replayWarn as tt, withResourceDeadline as u, resetAwsClients as un, isThrottlingError as ut, isFinalSnapshotError as v, LocalMigrateError as vn, stringifyValue as vt, isStatefulRecreateTargetSync as w, ProvisioningError as wn, AssetModeResolver as wt, makeCanonicalizePropertiesFn as x, MissingCdkCliError as xn, createAssetRedirectResolver as xt, refusesFinalSnapshot as y, LocalStartServiceError as yn, WorkGraph as yt, CloudControlProvider as z, Synthesizer as zt };
22592
+ //# sourceMappingURL=deploy-engine-DPnxkjWi.js.map