@go-to-k/cdkd 0.281.13 → 0.281.14

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.
@@ -216,6 +216,33 @@ function formatDuration(ms) {
216
216
  return minutes === 0 ? `${hours}h` : `${hours}h${minutes}m`;
217
217
  }
218
218
  /**
219
+ * A DELIBERATE refusal to resolve an intrinsic function, as opposed to a
220
+ * "the referenced thing does not exist" miss (issue
221
+ * [#1740](https://github.com/go-to-k/cdkd/issues/1740)).
222
+ *
223
+ * The distinction exists for exactly one consumer: `Fn::Sub`'s variable
224
+ * resolution, which speculatively tries `Ref` and then `Fn::GetAtt` and keeps
225
+ * the raw `${...}` placeholder when neither resolves. That warn-and-keep is the
226
+ * long-standing, deliberate behavior for a genuinely unknown variable — but a
227
+ * bare `catch` around it also swallowed every REFUSAL the resolver raises on
228
+ * purpose (`guardedPhysicalIdFallback`'s ARN / URL shape hard-fail, the
229
+ * `--strict-getatt` rejection, `rejectPlaceholderArnAttribute`), so a template
230
+ * that hard-fails when the reference sits in a resource property silently
231
+ * degraded to shipping a literal `${Resource.Attribute}` to AWS when the
232
+ * IDENTICAL reference was written inside an `Fn::Sub`.
233
+ *
234
+ * Throwing this class rather than a bare `Error` is what lets that catch
235
+ * re-raise a refusal (carrying its own message and remedy) while leaving the
236
+ * not-found path on warn-and-keep. Nothing else branches on it.
237
+ */
238
+ var IntrinsicResolutionRefusalError = class IntrinsicResolutionRefusalError extends CdkdError {
239
+ constructor(message, cause) {
240
+ super(message, "INTRINSIC_RESOLUTION_REFUSAL", cause);
241
+ this.name = "IntrinsicResolutionRefusalError";
242
+ Object.setPrototypeOf(this, IntrinsicResolutionRefusalError.prototype);
243
+ }
244
+ };
245
+ /**
219
246
  * Dependency resolution errors
220
247
  */
221
248
  var DependencyError = class DependencyError extends CdkdError {
@@ -9205,6 +9232,53 @@ async function applyRoleArnIfSet(opts) {
9205
9232
  }
9206
9233
  }
9207
9234
 
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
+
9208
9282
  //#endregion
9209
9283
  //#region src/provisioning/config-shape.ts
9210
9284
  /**
@@ -10712,44 +10786,103 @@ const cachedDynamicReferences = {};
10712
10786
  */
10713
10787
  const cachedEc2InstanceAttributes = {};
10714
10788
  /**
10789
+ * Re-derive the partition for a region the caller overrode (issue #1730).
10790
+ *
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.
10795
+ */
10796
+ function withOverrideRegion(info, region) {
10797
+ return {
10798
+ ...info,
10799
+ region,
10800
+ partition: derivePartitionAndUrlSuffix(region).partition
10801
+ };
10802
+ }
10803
+ /**
10804
+ * How long a FABRICATED answer is reused before STS is retried (issue #1730,
10805
+ * PR review). Deliberately not the success path's forever-cache — the whole
10806
+ * point is that a transient blip must not poison the run — but not zero either:
10807
+ * `getAccountInfo` is on the path of EVERY `Fn::GetAtt` and every
10808
+ * `AWS::AccountId` / `AWS::Partition` / `AWS::StackId` pseudo-parameter, so an
10809
+ * uncached failure re-issues `GetCallerIdentity` (with the SDK's own 3-attempt
10810
+ * retry + backoff) dozens of times per stack and prints one warning each. This
10811
+ * window collapses a burst into one call while still letting a later phase of
10812
+ * the same deploy heal.
10813
+ */
10814
+ const FABRICATED_ACCOUNT_INFO_TTL_MS = 1e4;
10815
+ /** Test seam for {@link FABRICATED_ACCOUNT_INFO_TTL_MS} expiry. */
10816
+ const accountInfoClock = { now: () => Date.now() };
10817
+ let fabricatedAccountInfo = null;
10818
+ /**
10819
+ * The single in-flight lookup, so N concurrent callers share ONE round trip.
10820
+ *
10821
+ * The TTL above collapses SEQUENTIAL callers; this collapses PARALLEL ones
10822
+ * (PR review). `cdkd deploy --concurrency 10` resolves ten resources' intrinsics
10823
+ * at once, so without it an STS outage costs ten `GetCallerIdentity` calls —
10824
+ * each with the SDK's own 3-attempt retry — and ten identical warnings per
10825
+ * window. Cleared in a `finally` so a failure cannot wedge it.
10826
+ */
10827
+ let accountInfoInFlight = null;
10828
+ /**
10715
10829
  * Get AWS account information from STS
10716
10830
  */
10717
10831
  async function getAccountInfo(overrideRegion) {
10718
- if (cachedAccountInfo) {
10719
- if (overrideRegion && overrideRegion !== cachedAccountInfo.region) return {
10720
- ...cachedAccountInfo,
10721
- region: overrideRegion
10722
- };
10723
- return cachedAccountInfo;
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);
10837
+ try {
10838
+ return forRegion(await accountInfoInFlight);
10839
+ } finally {
10840
+ accountInfoInFlight = null;
10724
10841
  }
10842
+ }
10843
+ async function resolveAccountInfo(overrideRegion) {
10725
10844
  const logger = getLogger().child("IntrinsicFunctionResolver");
10726
10845
  const stsClient = getAwsClients().sts;
10727
10846
  try {
10728
10847
  const response = await stsClient.send(new GetCallerIdentityCommand({}));
10729
10848
  const accountId = response.Account || "123456789012";
10730
10849
  const region = overrideRegion || process.env["AWS_REGION"] || "us-east-1";
10731
- const partition = "aws";
10732
- cachedAccountInfo = {
10850
+ const partition = derivePartitionAndUrlSuffix(region).partition;
10851
+ const resolved = {
10733
10852
  accountId,
10734
10853
  region,
10735
10854
  partition,
10736
10855
  ...response.Account ? {} : { fabricated: true }
10737
10856
  };
10738
- logger.debug(`Retrieved AWS account info: ${accountId}, ${region}, ${partition}`);
10739
- if (overrideRegion && overrideRegion !== region) return {
10740
- ...cachedAccountInfo,
10741
- region: overrideRegion
10857
+ if (resolved.fabricated) fabricatedAccountInfo = {
10858
+ info: resolved,
10859
+ expiresAt: accountInfoClock.now() + FABRICATED_ACCOUNT_INFO_TTL_MS
10742
10860
  };
10743
- return cachedAccountInfo;
10861
+ else {
10862
+ cachedAccountInfo = resolved;
10863
+ fabricatedAccountInfo = null;
10864
+ }
10865
+ logger.debug(`Retrieved AWS account info: ${accountId}, ${region}, ${partition}`);
10866
+ return resolved;
10744
10867
  } catch (error) {
10745
10868
  logger.warn(`Failed to get AWS account info from STS: ${error instanceof Error ? error.message : String(error)}, using defaults`);
10746
- cachedAccountInfo = {
10869
+ const region = overrideRegion || process.env["AWS_REGION"] || "us-east-1";
10870
+ const fallback = {
10747
10871
  accountId: process.env["AWS_ACCOUNT_ID"] || "123456789012",
10748
- region: overrideRegion || process.env["AWS_REGION"] || "us-east-1",
10749
- partition: "aws",
10872
+ region,
10873
+ partition: derivePartitionAndUrlSuffix(region).partition,
10750
10874
  ...process.env["AWS_ACCOUNT_ID"] ? {} : { fabricated: true }
10751
10875
  };
10752
- return cachedAccountInfo;
10876
+ if (fallback.fabricated) {
10877
+ if (!cachedAccountInfo) fabricatedAccountInfo = {
10878
+ info: fallback,
10879
+ expiresAt: accountInfoClock.now() + FABRICATED_ACCOUNT_INFO_TTL_MS
10880
+ };
10881
+ } else {
10882
+ cachedAccountInfo = fallback;
10883
+ fabricatedAccountInfo = null;
10884
+ }
10885
+ return fallback;
10753
10886
  }
10754
10887
  }
10755
10888
  /**
@@ -11135,7 +11268,7 @@ var IntrinsicFunctionResolver = class {
11135
11268
  }
11136
11269
  }
11137
11270
  }
11138
- const value = await this.constructAttribute(resource, attributeName, context, logicalId);
11271
+ const value = await this.constructGuardedAttribute(resource, attributeName, context, logicalId);
11139
11272
  this.logger.debug(`Resolved Fn::GetAtt: ${logicalId}.${attributeName} -> ${stringifyAttributeForLog(attributeName, value)}`);
11140
11273
  return value;
11141
11274
  }
@@ -11171,17 +11304,67 @@ var IntrinsicFunctionResolver = class {
11171
11304
  rejectPlaceholderArnAttribute(resource, attributeName, value, logicalId) {
11172
11305
  if (!REF_RETURNS_ARN_FROM_STATE.get(resource.resourceType)?.includes(attributeName)) return;
11173
11306
  if (typeof value !== "string" || !isPlaceholderArn(value)) return;
11174
- throw new Error(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resource.resourceType}: the recorded value "${value}" is a placeholder written by a cdkd version older than issue #1681 — its region and account fields are literal wildcards, so it is not a usable ARN. Deploy the stack again so the resource's next update heals the record (cdkd now records the real ARN), or re-import the resource.`);
11307
+ throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resource.resourceType}: the recorded value "${value}" is a placeholder written by a cdkd version older than issue #1681 — its region and account fields are literal wildcards, so it is not a usable ARN. Deploy the stack again so the resource's next update heals the record (cdkd now records the real ARN), or re-import the resource.`);
11175
11308
  }
11176
11309
  /**
11177
- * Construct resource attribute value based on resource type
11310
+ * Construct resource attribute value based on resource type, refusing to
11311
+ * SERVE one built from a fabricated account id (issue #1730).
11312
+ *
11313
+ * Thin wrapper over {@link constructAttribute}. ~30 branches there
11314
+ * build `arn:<partition>:<svc>:<region>:<accountId>:...`, and when the account
11315
+ * id is the hardcoded `123456789012` fallback the result is an ARN naming
11316
+ * SOMEONE ELSE'S account with no wildcard in it — so `isPlaceholderArn` cannot
11317
+ * catch it and every consumer downstream, the state record included, receives
11318
+ * a confidently wrong value. Refusing matches how
11319
+ * {@link guardedPhysicalIdFallback} already treats a knowably-wrong `*Arn`
11320
+ * (the #1103 class): a resource in this state has no correct value to serve.
11321
+ *
11322
+ * The test is on the CONSTRUCTED VALUE, not on the attribute NAME, and that
11323
+ * precision is the whole point: `AWS::S3::Bucket`'s `Arn` is
11324
+ * `arn:aws:s3:::<bucket>` with no account field, so a name-based `*Arn` guard
11325
+ * would refuse a value the fabricated id cannot corrupt. Everything the
11326
+ * account id does not appear in — `DomainName`, `Endpoint`, `WebsiteURL` —
11327
+ * keeps resolving unchanged.
11328
+ *
11329
+ * The match is a BARE substring rather than the colon-delimited `:<id>:` an
11330
+ * ARN uses, because not every account embedding is an ARN field: PR review
11331
+ * caught `AWS::ECR::Repository`'s `RepositoryUri`
11332
+ * (`<accountId>.dkr.ecr.<region>.amazonaws.com/<repo>`), the one such site in
11333
+ * this method, where a colon-delimited test served the fabricated URI and
11334
+ * silently nullified the `CloudControlProvider` omission of the SAME
11335
+ * attribute. The direction is deliberately fail-SAFE: refusing is the honest
11336
+ * answer whenever cdkd cannot confirm the account, so a value that merely
11337
+ * CONTAINS the placeholder digits (a physicalId recorded against the AWS
11338
+ * documentation account) is refused rather than served — and only while STS
11339
+ * is failing, when the deploy has bigger problems.
11340
+ *
11341
+ * NOTE the naming: the per-type construction below KEEPS the name
11342
+ * `constructAttribute` and this guard takes a new one, rather than the other
11343
+ * way round. `scripts/gen-sdk-attr-coverage.ts` collects the set of resource
11344
+ * types `constructAttribute` references to decide which `*Arn` attributes the
11345
+ * resolver can already answer, so renaming that method emptied its walk and
11346
+ * the critic reported fresh `gap`s for CloudTrail Trail / RDS DBCluster /
11347
+ * DBInstance (measured — the first cut of this change did exactly that).
11348
+ */
11349
+ async constructGuardedAttribute(resource, attributeName, context, logicalId) {
11350
+ const accountInfo = await getAccountInfo(this.resolverRegion);
11351
+ 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.`);
11353
+ return value;
11354
+ }
11355
+ /**
11356
+ * The per-resource-type attribute construction itself.
11178
11357
  *
11179
11358
  * Many CloudFormation attributes are not returned by Cloud Control API,
11180
11359
  * so we need to construct them manually.
11360
+ *
11361
+ * Reached only through {@link constructGuardedAttribute}, which vets the
11362
+ * result. Keep this method's NAME — `scripts/gen-sdk-attr-coverage.ts` reads
11363
+ * the resource types it references.
11181
11364
  */
11182
- async constructAttribute(resource, attributeName, _context, logicalId) {
11365
+ async constructAttribute(resource, attributeName, _context, logicalId, accountInfo) {
11183
11366
  const { resourceType, physicalId } = resource;
11184
- const { region, accountId, partition } = await getAccountInfo(this.resolverRegion);
11367
+ const { region, accountId, partition } = accountInfo;
11185
11368
  if (resourceType === "AWS::DynamoDB::Table" || resourceType === "AWS::DynamoDB::GlobalTable") switch (attributeName) {
11186
11369
  case "Arn": return `arn:${partition}:dynamodb:${region}:${accountId}:table/${physicalId}`;
11187
11370
  case "StreamArn": return;
@@ -11366,7 +11549,7 @@ var IntrinsicFunctionResolver = class {
11366
11549
  }
11367
11550
  if (resourceType === "AWS::ECR::Repository") switch (attributeName) {
11368
11551
  case "Arn": return `arn:${partition}:ecr:${region}:${accountId}:repository/${physicalId}`;
11369
- case "RepositoryUri": return `${accountId}.dkr.ecr.${region}.amazonaws.com/${physicalId}`;
11552
+ case "RepositoryUri": return `${accountId}.dkr.ecr.${region}.${derivePartitionAndUrlSuffix(region).urlSuffix}/${physicalId}`;
11370
11553
  default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId);
11371
11554
  }
11372
11555
  if (resourceType === "AWS::ECS::Cluster") switch (attributeName) {
@@ -11494,8 +11677,8 @@ var IntrinsicFunctionResolver = class {
11494
11677
  guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId) {
11495
11678
  const expectsArnShape = attributeName.endsWith("Arn") && !physicalId.startsWith("arn:");
11496
11679
  const expectsUrlShape = attributeName.endsWith("Url") && !/^https?:\/\//.test(physicalId);
11497
- if (expectsArnShape || expectsUrlShape) throw new Error(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${physicalId}" is not ${expectsArnShape ? "an ARN (arn:...)" : "a URL (http(s)://...)"}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${attributeName}.`);
11498
- if (this.strictGetAtt) throw new Error(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${physicalId}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${attributeName}.`);
11680
+ if (expectsArnShape || expectsUrlShape) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resourceType}: attributes are not enriched for this resource type, and the physical ID fallback "${physicalId}" is not ${expectsArnShape ? "an ARN (arn:...)" : "a URL (http(s)://...)"}. CloudFormation would return a different value here, so falling back to the physical ID would silently produce a wrong value (e.g. in stack Outputs). Avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${attributeName}.`);
11681
+ if (this.strictGetAtt) throw new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${attributeName}] for ${resourceType}: attributes are not enriched for this resource type, and --strict-getatt rejects the physical ID fallback "${physicalId}" (which may not be the value CloudFormation would return). Drop --strict-getatt to fall back with a warning, avoid this Fn::GetAtt, or file an issue at https://github.com/go-to-k/cdkd/issues so cdkd can enrich ${resourceType}.${attributeName}.`);
11499
11682
  this.physicalIdFallbackCount++;
11500
11683
  this.logger.warn(`Unknown attribute ${attributeName} for resource type ${resourceType}, returning physical ID`);
11501
11684
  return physicalId;
@@ -11519,6 +11702,17 @@ var IntrinsicFunctionResolver = class {
11519
11702
  return result;
11520
11703
  }
11521
11704
  /**
11705
+ * The warning emitted when `Fn::Sub` keeps a `${...}` placeholder verbatim.
11706
+ *
11707
+ * It carries the underlying reason (issue #1740 item 2): the old text
11708
+ * asserted `not found` for EVERY failure, which was the wrong cause whenever
11709
+ * the variable WAS found and its resolution failed for some other reason.
11710
+ * Deliberate refusals no longer reach this path at all — they re-throw.
11711
+ */
11712
+ subPlaceholderWarning(varName, error) {
11713
+ return `Fn::Sub variable ${varName} could not be resolved (${error instanceof Error ? error.message : String(error)}), keeping placeholder`;
11714
+ }
11715
+ /**
11522
11716
  * Resolve Fn::Sub intrinsic function
11523
11717
  *
11524
11718
  * Fn::Sub supports two forms:
@@ -11563,16 +11757,18 @@ var IntrinsicFunctionResolver = class {
11563
11757
  else try {
11564
11758
  const value = await this.resolveRef(varNameStr, context);
11565
11759
  replacement = String(value);
11566
- } catch {
11760
+ } catch (refError) {
11567
11761
  if (varNameStr.includes(".")) try {
11568
11762
  const value = await this.resolveGetAtt(varNameStr, context);
11569
11763
  replacement = String(value);
11570
- } catch {
11571
- this.logger.warn(`Fn::Sub variable ${varNameStr} not found, keeping placeholder`);
11764
+ } catch (getAttError) {
11765
+ if (getAttError instanceof IntrinsicResolutionRefusalError) throw getAttError;
11766
+ this.logger.warn(this.subPlaceholderWarning(varNameStr, getAttError));
11572
11767
  replacement = match[0];
11573
11768
  }
11574
11769
  else {
11575
- this.logger.warn(`Fn::Sub variable ${varNameStr} not found, keeping placeholder`);
11770
+ if (refError instanceof IntrinsicResolutionRefusalError) throw refError;
11771
+ this.logger.warn(this.subPlaceholderWarning(varNameStr, refError));
11576
11772
  replacement = match[0];
11577
11773
  }
11578
11774
  }
@@ -12178,9 +12374,9 @@ var IntrinsicFunctionResolver = class {
12178
12374
  case "AWS::StackName": return context?.stackName ?? "UnknownStack";
12179
12375
  case "AWS::StackId": {
12180
12376
  const info = await getAccountInfo(this.resolverRegion);
12181
- return `arn:aws:cloudformation:${info.region}:${info.accountId}:stack/${context?.stackName ?? "UnknownStack"}/cdkd`;
12377
+ return `arn:${info.partition}:cloudformation:${info.region}:${info.accountId}:stack/${context?.stackName ?? "UnknownStack"}/cdkd`;
12182
12378
  }
12183
- case "AWS::URLSuffix": return "amazonaws.com";
12379
+ case "AWS::URLSuffix": return derivePartitionAndUrlSuffix(this.resolverRegion).urlSuffix;
12184
12380
  case "AWS::NotificationARNs": return "";
12185
12381
  case "AWS::NoValue": return AWS_NO_VALUE;
12186
12382
  default: return;
@@ -13208,7 +13404,7 @@ var CloudControlProvider = class {
13208
13404
  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);
13209
13405
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
13210
13406
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
13211
- const { ASGProvider } = await import("./asg-provider-CsY1ahKM.js").then((n) => n.n);
13407
+ const { ASGProvider } = await import("./asg-provider-C1WbHit9.js").then((n) => n.n);
13212
13408
  await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
13213
13409
  return;
13214
13410
  }
@@ -13341,6 +13537,31 @@ var CloudControlProvider = class {
13341
13537
  }
13342
13538
  }
13343
13539
  /**
13540
+ * Account info for an ARN / URI this provider SYNTHESIZES and records, or
13541
+ * `undefined` when it must not be built (issue
13542
+ * [#1730](https://github.com/go-to-k/cdkd/issues/1730)).
13543
+ *
13544
+ * `getAccountInfo` falls back to a hardcoded `123456789012` when STS cannot
13545
+ * answer, and an ARN built from it is structurally valid with no wildcard in
13546
+ * any field — so `isPlaceholderArn` (issue #1681) cannot catch it and every
13547
+ * downstream consumer receives a confidently wrong value that is then
13548
+ * RECORDED into state as the resource's `Fn::GetAtt` answer.
13549
+ *
13550
+ * Omitting the attribute is the honest answer and mirrors
13551
+ * `AppSyncProvider.childImportAttributes`: the resolver's own
13552
+ * `guardedPhysicalIdFallback` then hard-fails an `*Arn` read with a message
13553
+ * naming the cause, instead of a green deploy shipping an ARN for someone
13554
+ * else's account, and the record heals on the resource's next update.
13555
+ */
13556
+ async accountInfoForSynthesizedArn(resourceType, attributeName, physicalId) {
13557
+ const accountInfo = await getAccountInfo();
13558
+ if (accountInfo.fabricated) {
13559
+ 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
+ return;
13561
+ }
13562
+ return accountInfo;
13563
+ }
13564
+ /**
13344
13565
  * Enrich resource attributes with computed values
13345
13566
  *
13346
13567
  * CC API GetResource returns property names that match CloudFormation
@@ -13419,9 +13640,11 @@ var CloudControlProvider = class {
13419
13640
  break;
13420
13641
  case "AWS::KMS::Key":
13421
13642
  if (!enriched["Arn"]) try {
13422
- const kmsAccountInfo = await getAccountInfo();
13423
- enriched["Arn"] = `arn:${kmsAccountInfo.partition}:kms:${kmsAccountInfo.region}:${kmsAccountInfo.accountId}:key/${physicalId}`;
13424
- this.logger.debug(`Enriched KMS Key Arn for ${physicalId}: ${String(enriched["Arn"])}`);
13643
+ const kmsAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "Arn", physicalId);
13644
+ if (kmsAccountInfo) {
13645
+ enriched["Arn"] = `arn:${kmsAccountInfo.partition}:kms:${kmsAccountInfo.region}:${kmsAccountInfo.accountId}:key/${physicalId}`;
13646
+ this.logger.debug(`Enriched KMS Key Arn for ${physicalId}: ${String(enriched["Arn"])}`);
13647
+ }
13425
13648
  } catch (error) {
13426
13649
  this.logger.debug(`Failed to construct KMS Key Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
13427
13650
  }
@@ -13435,15 +13658,20 @@ var CloudControlProvider = class {
13435
13658
  break;
13436
13659
  case "AWS::ECR::Repository":
13437
13660
  if (!enriched["Arn"]) try {
13438
- const ecrAccountInfo = await getAccountInfo();
13439
- enriched["Arn"] = `arn:${ecrAccountInfo.partition}:ecr:${ecrAccountInfo.region}:${ecrAccountInfo.accountId}:repository/${physicalId}`;
13440
- this.logger.debug(`Enriched ECR Repository Arn for ${physicalId}: ${String(enriched["Arn"])}`);
13661
+ const ecrAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "Arn", physicalId);
13662
+ if (ecrAccountInfo) {
13663
+ enriched["Arn"] = `arn:${ecrAccountInfo.partition}:ecr:${ecrAccountInfo.region}:${ecrAccountInfo.accountId}:repository/${physicalId}`;
13664
+ this.logger.debug(`Enriched ECR Repository Arn for ${physicalId}: ${String(enriched["Arn"])}`);
13665
+ }
13441
13666
  } catch (error) {
13442
13667
  this.logger.debug(`Failed to construct ECR Repository Arn: ${error instanceof Error ? error.message : String(error)}`);
13443
13668
  }
13444
13669
  if (!enriched["RepositoryUri"]) try {
13445
- const ecrAccountInfo = await getAccountInfo();
13446
- enriched["RepositoryUri"] = `${ecrAccountInfo.accountId}.dkr.ecr.${ecrAccountInfo.region}.amazonaws.com/${physicalId}`;
13670
+ const ecrAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "RepositoryUri", physicalId);
13671
+ if (ecrAccountInfo) {
13672
+ const { urlSuffix } = derivePartitionAndUrlSuffix(ecrAccountInfo.region);
13673
+ enriched["RepositoryUri"] = `${ecrAccountInfo.accountId}.dkr.ecr.${ecrAccountInfo.region}.${urlSuffix}/${physicalId}`;
13674
+ }
13447
13675
  } catch {}
13448
13676
  break;
13449
13677
  case "AWS::EC2::EIP":
@@ -13464,9 +13692,11 @@ var CloudControlProvider = class {
13464
13692
  break;
13465
13693
  case "AWS::Kinesis::Stream":
13466
13694
  if (!enriched["Arn"]) try {
13467
- const kinesisAccountInfo = await getAccountInfo();
13468
- enriched["Arn"] = `arn:${kinesisAccountInfo.partition}:kinesis:${kinesisAccountInfo.region}:${kinesisAccountInfo.accountId}:stream/${physicalId}`;
13469
- this.logger.debug(`Enriched Kinesis Stream Arn for ${physicalId}: ${String(enriched["Arn"])}`);
13695
+ const kinesisAccountInfo = await this.accountInfoForSynthesizedArn(resourceType, "Arn", physicalId);
13696
+ if (kinesisAccountInfo) {
13697
+ enriched["Arn"] = `arn:${kinesisAccountInfo.partition}:kinesis:${kinesisAccountInfo.region}:${kinesisAccountInfo.accountId}:stream/${physicalId}`;
13698
+ this.logger.debug(`Enriched Kinesis Stream Arn for ${physicalId}: ${String(enriched["Arn"])}`);
13699
+ }
13470
13700
  } catch (error) {
13471
13701
  this.logger.debug(`Failed to construct Kinesis Stream Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
13472
13702
  }
@@ -20082,7 +20312,7 @@ const FLUSH_INTERVAL_MS = 2e3;
20082
20312
  const FLUSH_EVENT_THRESHOLD = 50;
20083
20313
  /** Build-time cdkd version, with a dev fallback for non-built contexts. */
20084
20314
  function getCdkdVersion() {
20085
- return "0.281.13";
20315
+ return "0.281.14";
20086
20316
  }
20087
20317
  /**
20088
20318
  * Generate a time-sortable unique run id, e.g.
@@ -22250,5 +22480,5 @@ var DeployEngine = class {
22250
22480
  };
22251
22481
 
22252
22482
  //#endregion
22253
- export { replayWarn as $, uploadCfnTemplate as $t, green as A, normalizeAwsError as An, formatDockerLoginError as At, slowCcOperationTimeoutMs as B, resolveApp as Bt, MULTI_REGION_RECREATE_BLOCKED_TYPES as C, ResourceUpdateNotSupportedError as Cn, BOOTSTRAP_MARKER_PREFIX as Ct, bold as D, SynthesisError as Dn, validateAssetBucketName as Dt, formatResourceLine as E, StateError as En, parseBootstrapMarker as Et, clearOnUpdateRemoval as F, getDockerImageBySourceHash as Ft, getAccountInfo as G, resolveStateBucketWithDefaultAndSource as Gt, isTerminationProtectionPropagationError as H, resolveCaptureObservedState as Ht, ProviderRegistry as I, Synthesizer as It, normalizeAwsTagsToCfn as J, warnDeprecatedNoPrefixCliFlag as Jt, refStateLookupFromResource as K, resolveUseCdkBootstrapAssets as Kt, findActionableSilentDrops as L, synthesisStatusMessage as Lt, yellow as M, __exportAll as Mn, runDockerForeground as Mt, IAMRoleProvider as N, runDockerStreaming as Nt, cyan as O, formatError as On, validateContainerRepoName as Ot, collectInlinePolicyNamesManagedBySiblings as P, AssetManifestLoader as Pt, readConfigString as Q, findLargeInlineResources as Qt, findSilentDropProperties as R, getDefaultStateBucketName as Rt, extractDeploymentEventError as S, ResourceTimeoutError as Sn, AssetModeResolver as St, renderStatefulReason as T, StackTerminationProtectionError as Tn, getBootstrapMarkerKey as Tt, IntrinsicFunctionResolver as U, resolveSkipPrefix as Ut, disableInstanceApiTermination as V, resolveAutoAssetStorage as Vt, cfnRefValueFromPhysicalId as W, resolveStateBucketWithDefault as Wt, assertRegionMatch as X, CFN_TEMPLATE_URL_LIMIT as Xt, resolveExplicitPhysicalId as Y, CFN_TEMPLATE_BODY_LIMIT as Yt, configStringRefusal as Z, MIGRATE_TMP_PREFIX as Zt, createPreDeleteFinalSnapshot as _, LockError as _n, WorkGraph as _t, DeploymentEventsStore as a, AwsClients as an, describeTypeWithThrottleRetry as at, unsupportedFinalSnapshotError as b, PartialFailureError as bn, loadPublishableAssetManifest as bt, replayFailedOperations as c, setAwsClients as cn, isThrottlingError as ct, IMPLICIT_DELETE_DEPENDENCIES as d, ConfigError as dn, LockManager as dt, expectedOwnerParam as en, requireConfigArray as et, computeImplicitDeleteEdges as f, DependencyError as fn, S3StateBackend as ft, ccRoutedFinalSnapshotError as g, LocalStartServiceError as gn, stringifyValue as gt, buildFinalSnapshotIdentifier as h, LocalMigrateError as hn, AssetPublisher as ht, DeploymentEventsReader as i, resolveBucketRegion as in, DiffCalculator as it, red as j, withErrorHandling as jn, getDockerCmd as jt, gray as k, isCdkdError as kn, buildDockerImage as kt, replayRollback as l, AssetError as ln, DagBuilder as lt, PRE_DELETE_SNAPSHOT_TYPES as m, LocalInvokeBuildError as mn, shouldRetainResource as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, processStackMessages as nn, requireConfigString as nt, planFailedOps as o, getAwsClients as on, withRetry as ot, ATOMIC_FINAL_SNAPSHOT_TYPES as p, DeployCancelledError as pn, rebuildClientForBucketRegion as pt, WAFv2WebACLProvider as q, stateBucketExistenceConfirmed as qt, DeployEngine as r, clearBucketRegionCache as rn, applyRoleArnIfSet as rt, planRollback as s, resetAwsClients as sn, isRetryableTransientError as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, AssemblyReader as tn, requireConfigObject as tt, withResourceDeadline as u, CdkdError as un, TemplateParser as ut, isFinalSnapshotError as v, MissingCdkCliError as vn, buildAssetRedirectMap as vt, isStatefulRecreateTargetSync as w, StackHasActiveImportsError as wn, ensureAssetStorage as wt, makeCanonicalizePropertiesFn as x, ProvisioningError as xn, rewriteTemplateAssetReferences as xt, refusesFinalSnapshot as y, NestedStackChildDirectDestroyError as yn, createAssetRedirectResolver as yt, CloudControlProvider as z, getLegacyStateBucketName as zt };
22254
- //# sourceMappingURL=deploy-engine-BWM9RxAW.js.map
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-3BZtmJqZ.js.map