@go-to-k/cdkd 0.291.17 → 0.291.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.
@@ -1,6 +1,6 @@
1
- import { C as displayAwsMessage, D as truncateCodePoints, E as isPasteableIdent, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-GASr9bAF.js";
1
+ import { C as displayAwsMessage, D as isPasteableIdent, E as displayStackName, O as truncateCodePoints, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-Bt0RGDfb.js";
2
2
  import { a as setAssumedRoleCredentials, c as currentStackAwsScope, i as getPreAssumeEnvCredentials, l as runInStackAwsScope, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, s as ambientRegion, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-jISU4PHo.js";
3
- import { t as getCdkdVersion } from "./version-DMRkEtU2.js";
3
+ import { t as getCdkdVersion } from "./version-2QIPw29e.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -35,10 +35,9 @@ import { GetHostedZoneCommand, ListHostedZonesByNameCommand, Route53Client } fro
35
35
  import { DescribeListenersCommand, DescribeLoadBalancersCommand, ElasticLoadBalancingV2Client } from "@aws-sdk/client-elastic-load-balancing-v2";
36
36
  import { KMSClient, ListAliasesCommand } from "@aws-sdk/client-kms";
37
37
  import { readFile } from "fs/promises";
38
- import { isAbsolute as isAbsolute$1, relative, resolve as resolve$1, sep } from "path";
38
+ import { isAbsolute as isAbsolute$1, resolve as resolve$1 } from "path";
39
39
  import { CreateRepositoryCommand, DescribeImagesCommand as DescribeImagesCommand$1, DescribeRepositoriesCommand, ECRClient, GetAuthorizationTokenCommand, PutImageTagMutabilityCommand } from "@aws-sdk/client-ecr";
40
40
  import { inspect } from "node:util";
41
- import { realpathSync as realpathSync$1 } from "fs";
42
41
  import { hostname } from "os";
43
42
  import graphlib from "graphlib";
44
43
  import { DescribeDBClustersCommand, DescribeDBInstancesCommand, RDSClient } from "@aws-sdk/client-rds";
@@ -3365,7 +3364,9 @@ function assemblyPathEscape(base, bound, candidate) {
3365
3364
  * Modifier Letters block (U+02BA reads as `"`, U+02BC as `'`) plus the ones
3366
3365
  * outside it (U+0374, U+0559, U+07F4-U+07F5, U+A78B-U+A78C, and the halfwidth
3367
3366
  * sound marks U+FF9E-U+FF9F). Not all of `\p{Lm}`: U+30FC is in it, and it is
3368
- * an ordinary character of a Japanese directory name.
3367
+ * an ordinary character of a Japanese directory name. `classify` tests
3368
+ * {@link DEFAULT_IGNORABLE} first, so that carve-out is a backstop here, kept
3369
+ * so the class matches cdk-local's `displayUntrustedValue`.
3369
3370
  */
3370
3371
  const VISIBLE_LETTER = new RegExp(String.raw`^(?![\p{Default_Ignorable_Code_Point}\u02b0-\u02ff\u0374\u0559\u07f4\u07f5\ua78b\ua78c\uff9e\uff9f])[\p{L}\p{N}]$`, "u");
3371
3372
  /**
@@ -3375,6 +3376,14 @@ const VISIBLE_LETTER = new RegExp(String.raw`^(?![\p{Default_Ignorable_Code_Poin
3375
3376
  */
3376
3377
  const COMBINING_MARK = /^\p{M}$/u;
3377
3378
  /**
3379
+ * A default-ignorable code point, which draws as NOTHING. Tested before the
3380
+ * mark rule, because some are combining marks (U+034F, U+17B4, U+180B, the
3381
+ * variation selectors U+FE00-U+FE0F and U+E0100-U+E01EF): after a letter they
3382
+ * would otherwise pass as bare, and `/home/me/.ss\u034fh` would print exactly
3383
+ * like `.ssh` while naming a different path (go-to-k/cdkd#3656).
3384
+ */
3385
+ const DEFAULT_IGNORABLE = /^\p{Default_Ignorable_Code_Point}$/u;
3386
+ /**
3378
3387
  * The ASCII a bare path may carry besides letters and digits. Everything else —
3379
3388
  * any whitespace, any quote, any other symbol — takes the boundary, which
3380
3389
  * costs a legitimate path nothing but a pair of quotes.
@@ -3394,6 +3403,10 @@ const BARE_PUNCTUATION = /^[/\\._~+@:=-]$/;
3394
3403
  function classify(chars) {
3395
3404
  let afterLetter = false;
3396
3405
  return chars.map((ch) => {
3406
+ if (DEFAULT_IGNORABLE.test(ch)) {
3407
+ afterLetter = false;
3408
+ return "escaped";
3409
+ }
3397
3410
  if (COMBINING_MARK.test(ch)) return afterLetter ? "bare" : "escaped";
3398
3411
  afterLetter = VISIBLE_LETTER.test(ch);
3399
3412
  if (afterLetter || BARE_PUNCTUATION.test(ch)) return "bare";
@@ -3944,7 +3957,7 @@ var AssemblyReader = class {
3944
3957
  getStack(assemblyDir, manifest, stackName) {
3945
3958
  const { stacks, failedStages } = this.readAssembly(assemblyDir, manifest);
3946
3959
  const stack = stacks.find((s) => s.stackName === stackName);
3947
- if (!stack) throw new SynthesisError(`Stack '${displaySafe(stackName)}' not found in assembly. ` + (stacks.length > 0 ? `Available: ${stacks.map((s) => displayIdent(s.stackName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS })).join(", ")}` : "The assembly has no stacks") + failedStageNote([stackName], failedStages));
3960
+ if (!stack) throw new SynthesisError(`Stack ${displayStackName(stackName)} not found in assembly. ` + (stacks.length > 0 ? `Available: ${stacks.map((s) => displayIdent(s.stackName, { maxCodePoints: STACK_REF_MAX_CODE_POINTS })).join(", ")}` : "The assembly has no stacks") + failedStageNote([stackName], failedStages));
3948
3961
  return stack;
3949
3962
  }
3950
3963
  /**
@@ -3964,7 +3977,7 @@ var AssemblyReader = class {
3964
3977
  const props = artifact.properties;
3965
3978
  if (props?.file) {
3966
3979
  const resolved = resolveAssemblyPath(assemblyDir, props.file);
3967
- if (!resolved.contained) throw new SynthesisError(`Asset manifest artifact '${displaySafe(artifactId)}' has file=${displayAssemblyPath(props.file)} which ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
3980
+ if (!resolved.contained) throw new SynthesisError(`Asset manifest artifact ${displayStackName(artifactId)} has file=${displayAssemblyPath(props.file)} which ${renderAssemblyPathEscape(resolved, assemblyDir)}`);
3968
3981
  map.set(artifactId, resolved.path);
3969
3982
  }
3970
3983
  }
@@ -3977,16 +3990,16 @@ var AssemblyReader = class {
3977
3990
  const props = artifact.properties;
3978
3991
  const stackName = props?.stackName || artifactId;
3979
3992
  const templateFile = props?.templateFile;
3980
- if (!templateFile) throw new SynthesisError(`Stack '${displaySafe(stackName)}' has no templateFile property`);
3993
+ if (!templateFile) throw new SynthesisError(`Stack ${displayStackName(stackName)} has no templateFile property`);
3981
3994
  const resolvedTemplate = resolveAssemblyPath(assemblyDir, templateFile);
3982
- if (!resolvedTemplate.contained) throw new SynthesisError(`Stack '${displaySafe(stackName)}' has templateFile=${displayAssemblyPath(templateFile)} which ${renderAssemblyPathEscape(resolvedTemplate, assemblyDir)}`);
3995
+ if (!resolvedTemplate.contained) throw new SynthesisError(`Stack ${displayStackName(stackName)} has templateFile=${displayAssemblyPath(templateFile)} which ${renderAssemblyPathEscape(resolvedTemplate, assemblyDir)}`);
3983
3996
  const templatePath = resolvedTemplate.path;
3984
3997
  let template;
3985
3998
  try {
3986
3999
  const content = readFileSync(templatePath, "utf-8");
3987
4000
  template = JSON.parse(content);
3988
4001
  } catch (error) {
3989
- throw new SynthesisError(`Failed to read template for stack '${displaySafe(stackName)}': ${displaySafe(error instanceof Error ? error.message : String(error))}`, error instanceof Error ? error : void 0);
4002
+ throw new SynthesisError(`Failed to read template for stack ${displayStackName(stackName)}: ${displaySafe(error instanceof Error ? error.message : String(error))}`, error instanceof Error ? error : void 0);
3990
4003
  }
3991
4004
  this.logger.debug(`Stack: ${displaySafe(stackName)}, Resources: ${Object.keys(template.Resources ?? {}).length}`);
3992
4005
  let assetManifestPath;
@@ -4005,7 +4018,7 @@ var AssemblyReader = class {
4005
4018
  if (depName !== stackName) dependencyNames.push(depName);
4006
4019
  }
4007
4020
  }
4008
- if (dependencyNames.length > 0) this.logger.debug(`Stack '${displaySafe(stackName)}' depends on: [${dependencyNames.map((n) => displaySafe(n)).join(", ")}]`);
4021
+ if (dependencyNames.length > 0) this.logger.debug(`Stack ${displayStackName(stackName)} depends on: [${dependencyNames.map((n) => displaySafe(n)).join(", ")}]`);
4009
4022
  let env;
4010
4023
  if (artifact.environment) env = parseEnvironment(artifact.environment);
4011
4024
  const nestedTemplates = nullPrototypeRecord();
@@ -4013,9 +4026,9 @@ var AssemblyReader = class {
4013
4026
  if (resource?.Type !== "AWS::CloudFormation::Stack") continue;
4014
4027
  const assetPath = resource.Metadata?.["aws:asset:path"];
4015
4028
  if (typeof assetPath !== "string" || assetPath.length === 0) continue;
4016
- if (isAbsolute(assetPath) || /^[a-zA-Z]:[\\/]/.test(assetPath) || assetPath.startsWith("\\\\")) throw new SynthesisError(`Stack '${displaySafe(stackName)}' nested-stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']=${displayAssemblyPath(assetPath)} which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
4029
+ if (isAbsolute(assetPath) || /^[a-zA-Z]:[\\/]/.test(assetPath) || assetPath.startsWith("\\\\")) throw new SynthesisError(`Stack ${displayStackName(stackName)} nested-stack ${displayIdent(logicalId)} has Metadata['aws:asset:path']=${displayAssemblyPath(assetPath)} which is absolute. CDK emits relative asset paths for nested templates; an absolute path indicates the synth output was hand-modified or generated by a non-CDK toolchain. Refusing to load.`);
4017
4030
  const resolvedNested = resolveAssemblyPath(assemblyDir, assetPath);
4018
- if (!resolvedNested.contained) throw new SynthesisError(`Stack '${displaySafe(stackName)}' nested-stack '${displaySafe(logicalId)}' has Metadata['aws:asset:path']=${displayAssemblyPath(assetPath)} which ${renderAssemblyPathEscape(resolvedNested, assemblyDir)}`);
4031
+ if (!resolvedNested.contained) throw new SynthesisError(`Stack ${displayStackName(stackName)} nested-stack ${displayIdent(logicalId)} has Metadata['aws:asset:path']=${displayAssemblyPath(assetPath)} which ${renderAssemblyPathEscape(resolvedNested, assemblyDir)}`);
4019
4032
  nestedTemplates[logicalId] = resolvedNested.path;
4020
4033
  }
4021
4034
  return {
@@ -4107,6 +4120,109 @@ var ContextStore = class {
4107
4120
  }
4108
4121
  };
4109
4122
 
4123
+ //#endregion
4124
+ //#region src/utils/ambient-client-defaults.ts
4125
+ /**
4126
+ * Client config for an SDK client built OUTSIDE `AwsClients` (issue
4127
+ * [#3588](https://github.com/go-to-k/cdkd/issues/3588)).
4128
+ *
4129
+ * `awsClientDefaults()` alone carries the ambient ENVIRONMENT — `AWS_PROFILE`,
4130
+ * a published `--role-arn` role, the proxy handler, a stack scope's region — but
4131
+ * not an explicit `AwsClientConfig.credentials` object, which has no
4132
+ * environment path at all. A LIBRARY caller that installs
4133
+ * `new AwsClients({ credentials })` (`AwsClients` / `setAwsClients` are public
4134
+ * exports) therefore had every client a provider, context provider,
4135
+ * asset publisher or the cross-account STS hop built for itself signing with
4136
+ * the default credential chain: a different identity from the one it
4137
+ * configured. {@link ambientClientDefaults} closes that for every site at once
4138
+ * by reading the ACTIVE clients' {@link AwsClients.credentialConfig}.
4139
+ *
4140
+ * BOUND: a site that caches its client (every SDK provider's lazy
4141
+ * `getClient()`) captures the identity active at its FIRST call. cdkd builds a
4142
+ * provider registry per stack inside that stack's scope, so this is the stack's
4143
+ * identity; a library caller that reuses one registry across `setAwsClients`
4144
+ * switches keeps the first identity's clients.
4145
+ *
4146
+ * SPREAD IT FIRST, exactly where `awsClientDefaults()` was spread:
4147
+ *
4148
+ * `new GlueClient({ ...ambientClientDefaults(), ...(region && { region }) })`
4149
+ *
4150
+ * The site's own keys still win, so a site that names its own `region` or its
4151
+ * own `credentials` keeps them. `scripts/check-aws-client-defaults.ts` accepts
4152
+ * this helper as the defaults spread (it calls `awsClientDefaults` afresh per
4153
+ * call, so each client still gets its own routing agent), and
4154
+ * `tests/unit/utils/ambient-client-defaults-fence.test.ts` refuses a new
4155
+ * `awsClientDefaults()`-only construction under `src/**`.
4156
+ *
4157
+ * Its own module rather than a member of `aws-clients.ts` so that a test which
4158
+ * `vi.mock`s `aws-clients.js` with a `getAwsClients` double still reaches this
4159
+ * code and degrades, instead of losing the export. It is deliberately OUTSIDE
4160
+ * the literal-`.ts` closure `scripts/audit-provider-coverage.ts` resolves
4161
+ * (nothing in that closure imports it), and neither module it imports imports
4162
+ * it back, so there is no cycle.
4163
+ */
4164
+ /**
4165
+ * The ACTIVE clients' credential configuration: the stack scope's
4166
+ * `AwsClients` inside `runWithStackAwsClients`, else the process-global one.
4167
+ *
4168
+ * `{}` when those clients carry none (every CLI run passes no explicit
4169
+ * `credentials`, and `--profile` alone yields `{ profile }`), and also for a
4170
+ * test double that has no `credentialConfig` getter — degrading to the plain
4171
+ * `awsClientDefaults()` answer rather than throwing.
4172
+ *
4173
+ * Read through `getAwsClients()`, which creates an unconfigured global on first
4174
+ * use when nothing installed one. That is side-effect free here: its service
4175
+ * clients are lazy, no production call passes `getAwsClients(config)` (so no
4176
+ * later configuration is shadowed), and `setAwsClients` replaces it.
4177
+ */
4178
+ function ambientCredentialConfig() {
4179
+ return getAwsClients()?.credentialConfig ?? {};
4180
+ }
4181
+ /**
4182
+ * `awsClientDefaults(...)` plus `credentialConfig`, in `AwsClients.clientOptions`'
4183
+ * spread order: the defaults FIRST, so an explicit `credentials` outranks a
4184
+ * published `--role-arn` role, then `profile`, then `credentials`.
4185
+ *
4186
+ * Takes the configuration as an argument for the callers that must key a cache
4187
+ * on the SAME reading they build from ({@link credentialFingerprint}); every
4188
+ * other site calls {@link ambientClientDefaults}.
4189
+ */
4190
+ function clientDefaultsFor(credentialConfig) {
4191
+ return {
4192
+ ...awsClientDefaults({ profile: credentialConfig.profile }),
4193
+ ...credentialConfig.profile && { profile: credentialConfig.profile },
4194
+ ...credentialConfig.credentials && { credentials: { ...credentialConfig.credentials } }
4195
+ };
4196
+ }
4197
+ /**
4198
+ * The config every SDK client built outside `AwsClients` spreads first: the
4199
+ * proxy / role / scope-region defaults plus the ACTIVE clients' profile and
4200
+ * explicit credentials.
4201
+ */
4202
+ function ambientClientDefaults() {
4203
+ return clientDefaultsFor(ambientCredentialConfig());
4204
+ }
4205
+ /**
4206
+ * A cache-key component naming WHICH identity a credential configuration
4207
+ * selects, for a cache that holds a client (or credentials) built from it.
4208
+ *
4209
+ * `profile` plus the explicit credentials' ACCESS KEY ID — never the secret
4210
+ * key or the session token. The access key id is an identifier rather than a
4211
+ * secret (`expected-bucket-owner.ts` already keys its account cache on it), and
4212
+ * STS issues a distinct one per session, so two different explicit bags are
4213
+ * told apart. The one collision is two bags sharing an access key id with
4214
+ * different secrets, at most one of which can be valid.
4215
+ *
4216
+ * What it does NOT carry, because it is PROCESS-WIDE rather than per
4217
+ * configuration: a published `--role-arn` role (set once, refused a second
4218
+ * time), the proxy, and `AWS_PROFILE` / the environment chain. Two readings
4219
+ * with equal fingerprints within one process therefore resolve to the same
4220
+ * identity. Do not log it.
4221
+ */
4222
+ function credentialFingerprint(credentialConfig) {
4223
+ return JSON.stringify([credentialConfig.profile ?? null, credentialConfig.credentials?.accessKeyId ?? null]);
4224
+ }
4225
+
4110
4226
  //#endregion
4111
4227
  //#region src/synthesis/context-providers/az-provider.ts
4112
4228
  /**
@@ -4125,7 +4241,7 @@ var AZContextProvider = class {
4125
4241
  const region = props["region"] || this.awsConfig?.region;
4126
4242
  this.logger.debug(`Fetching availability zones for region: ${region}`);
4127
4243
  const client = new EC2Client({
4128
- ...awsClientDefaults(),
4244
+ ...ambientClientDefaults(),
4129
4245
  ...region && { region }
4130
4246
  });
4131
4247
  try {
@@ -4158,7 +4274,7 @@ var SSMContextProvider = class {
4158
4274
  if (!parameterName) throw new Error("SSM context provider requires parameterName property");
4159
4275
  this.logger.debug(`Reading SSM parameter: ${parameterName} (region: ${region})`);
4160
4276
  const client = new SSMClient({
4161
- ...awsClientDefaults(),
4277
+ ...ambientClientDefaults(),
4162
4278
  ...region && { region }
4163
4279
  });
4164
4280
  try {
@@ -4200,7 +4316,7 @@ var HostedZoneContextProvider = class {
4200
4316
  if (!domainName) throw new Error("Hosted zone context provider requires domainName property");
4201
4317
  this.logger.debug(`Looking up hosted zone: ${domainName} (private: ${privateZone})`);
4202
4318
  const client = new Route53Client({
4203
- ...awsClientDefaults(),
4319
+ ...ambientClientDefaults(),
4204
4320
  ...region && { region }
4205
4321
  });
4206
4322
  try {
@@ -4254,7 +4370,7 @@ var VpcContextProvider = class {
4254
4370
  const returnVpnGateways = props["returnVpnGateways"];
4255
4371
  this.logger.debug(`Looking up VPC (region: ${region}, filter: ${JSON.stringify(filter)})`);
4256
4372
  const client = new EC2Client({
4257
- ...awsClientDefaults(),
4373
+ ...ambientClientDefaults(),
4258
4374
  ...region && { region }
4259
4375
  });
4260
4376
  try {
@@ -4420,7 +4536,7 @@ var CcApiContextProvider = class {
4420
4536
  if (!typeName) throw new Error("CC API context provider requires typeName property");
4421
4537
  this.logger.debug(`CC API lookup: ${typeName}${exactIdentifier ? ` (id: ${exactIdentifier})` : ""} (region: ${region})`);
4422
4538
  const client = new CloudControlClient({
4423
- ...awsClientDefaults(),
4539
+ ...ambientClientDefaults(),
4424
4540
  ...region && { region }
4425
4541
  });
4426
4542
  try {
@@ -4547,7 +4663,7 @@ var AmiContextProvider = class {
4547
4663
  const filters = props["filters"];
4548
4664
  this.logger.debug(`Looking up AMI (region: ${region})`);
4549
4665
  const client = new EC2Client({
4550
- ...awsClientDefaults(),
4666
+ ...ambientClientDefaults(),
4551
4667
  ...region && { region }
4552
4668
  });
4553
4669
  try {
@@ -4590,7 +4706,7 @@ var SecurityGroupContextProvider = class {
4590
4706
  const vpcId = props["vpcId"];
4591
4707
  this.logger.debug(`Looking up security group (id: ${securityGroupId}, name: ${securityGroupName}, region: ${region})`);
4592
4708
  const client = new EC2Client({
4593
- ...awsClientDefaults(),
4709
+ ...ambientClientDefaults(),
4594
4710
  ...region && { region }
4595
4711
  });
4596
4712
  try {
@@ -4644,7 +4760,7 @@ var LoadBalancerContextProvider = class {
4644
4760
  const loadBalancerType = props["loadBalancerType"];
4645
4761
  this.logger.debug(`Looking up load balancer (arn: ${loadBalancerArn}, region: ${region})`);
4646
4762
  const client = new ElasticLoadBalancingV2Client({
4647
- ...awsClientDefaults(),
4763
+ ...ambientClientDefaults(),
4648
4764
  ...region && { region }
4649
4765
  });
4650
4766
  try {
@@ -4686,7 +4802,7 @@ var LoadBalancerListenerContextProvider = class {
4686
4802
  const listenerProtocol = props["listenerProtocol"];
4687
4803
  this.logger.debug(`Looking up load balancer listener (arn: ${listenerArn}, lb: ${loadBalancerArn}, region: ${region})`);
4688
4804
  const client = new ElasticLoadBalancingV2Client({
4689
- ...awsClientDefaults(),
4805
+ ...ambientClientDefaults(),
4690
4806
  ...region && { region }
4691
4807
  });
4692
4808
  try {
@@ -4730,7 +4846,7 @@ var KeyContextProvider = class {
4730
4846
  if (!aliasName) throw new Error("Key context provider requires aliasName property");
4731
4847
  this.logger.debug(`Looking up KMS key by alias: ${aliasName} (region: ${region})`);
4732
4848
  const client = new KMSClient({
4733
- ...awsClientDefaults(),
4849
+ ...ambientClientDefaults(),
4734
4850
  ...region && { region }
4735
4851
  });
4736
4852
  try {
@@ -5115,6 +5231,10 @@ function canonicalizeRegion(region) {
5115
5231
  * the COMMERCIAL suffix, so `parseEcrRegistryHost` (`src/utils/ecr-uri.ts`)
5116
5232
  * rejected it under the strict host check issue #1758 added and the image was
5117
5233
  * classified `public` — anonymous pull, no `docker login`, opaque failure.
5234
+ * `parseEcrRegistryHost` is stricter than this fallback (issue #3670): a
5235
+ * region whose PREFIX no partition row knows (`nz-north-1`) is refused there,
5236
+ * and reported by `looksLikeEcrHostWithForeignSuffix`, until its partition's
5237
+ * `regionRegex` is added to `AWS_REGION_ID` in `src/utils/ecr-uri.ts`.
5118
5238
  *
5119
5239
  * The region is CANONICALIZED through {@link canonicalizeRegion} before the
5120
5240
  * prefix tests (issue [#1795](https://github.com/go-to-k/cdkd/issues/1795)),
@@ -6630,7 +6750,7 @@ async function expandMacrosAttempt(template, opts, logger) {
6630
6750
  const parameters = buildParameterValues(template, logger);
6631
6751
  ownsClient = opts.cfnClient === void 0;
6632
6752
  cfn = opts.cfnClient ?? new CloudFormationClient({
6633
- ...awsClientDefaults(),
6753
+ ...ambientClientDefaults(),
6634
6754
  region
6635
6755
  });
6636
6756
  let templateInput;
@@ -7378,12 +7498,12 @@ var Synthesizer = class {
7378
7498
  else if (accountId) stateBucket = `cdkd-state-${accountId}`;
7379
7499
  else {
7380
7500
  const oversize = stacksWithMacros.find((s) => JSON.stringify(s.template).length > 51200);
7381
- if (oversize) throw new SynthesisError(`Stack '${displaySafe(oversize.stackName)}' uses CloudFormation macros AND its serialized template exceeds the 51,200-byte inline TemplateBody limit, so cdkd must upload the template to S3 for the transient expansion changeset. cdkd could not resolve a state bucket: STS GetCallerIdentity failed AND --state-bucket was not provided. Pass --state-bucket <name> (cdkd uses the same bucket as cdkd deploy state storage; typically 'cdkd-state-<accountId>').`);
7501
+ if (oversize) throw new SynthesisError(`Stack ${displayStackName(oversize.stackName)} uses CloudFormation macros AND its serialized template exceeds the 51,200-byte inline TemplateBody limit, so cdkd must upload the template to S3 for the transient expansion changeset. cdkd could not resolve a state bucket: STS GetCallerIdentity failed AND --state-bucket was not provided. Pass --state-bucket <name> (cdkd uses the same bucket as cdkd deploy state storage; typically 'cdkd-state-<accountId>').`);
7382
7502
  stateBucket = void 0;
7383
7503
  }
7384
7504
  for (const stack of stacksWithMacros) {
7385
7505
  const macros = enumerateMacros(stack.template);
7386
- this.logger.info(`[macros] Expanding CloudFormation macros for stack '${displaySafe(stack.stackName)}' via CFn round-trip (transforms: ${macros.map((m) => displaySafe(m)).join(", ")}; may take 30-60s)...`);
7506
+ this.logger.info(`[macros] Expanding CloudFormation macros for stack ${displayStackName(stack.stackName)} via CFn round-trip (transforms: ${macros.map((m) => displaySafe(m)).join(", ")}; may take 30-60s)...`);
7387
7507
  const before = Date.now();
7388
7508
  const expanded = await expandMacros(stack.template, {
7389
7509
  region,
@@ -7536,14 +7656,14 @@ function resolveFileAssetSourcePath(manifestDir, asset, opts) {
7536
7656
  const absolute = resolve$1(asset.source.path);
7537
7657
  const escape = absoluteAssemblyPathEscape(assetOutdir, absolute);
7538
7658
  if (escape !== void 0) warnAbsoluteAssetPath({
7539
- subject: `File asset '${displaySafe(asset.displayName)}'`,
7659
+ subject: `File asset ${displayAssemblyPath(asset.displayName)}`,
7540
7660
  field: "source.path",
7541
7661
  absolute,
7542
7662
  escape,
7543
7663
  sink
7544
7664
  });
7545
7665
  else if (namesTheSameDirectory(assetOutdir, absolute)) warnWholeAssemblyAsSource({
7546
- subject: `File asset '${displaySafe(asset.displayName)}'`,
7666
+ subject: `File asset ${displayAssemblyPath(asset.displayName)}`,
7547
7667
  field: "source.path",
7548
7668
  outdir: absolute,
7549
7669
  sink
@@ -7553,14 +7673,14 @@ function resolveFileAssetSourcePath(manifestDir, asset, opts) {
7553
7673
  const resolved = resolveAssemblyPath(manifestDir, asset.source.path, { containWithin: assetOutdir });
7554
7674
  if (!resolved.contained && namesTheSameDirectory(assetOutdir, resolved.path)) {
7555
7675
  warnWholeAssemblyAsSource({
7556
- subject: `File asset '${displaySafe(asset.displayName)}'`,
7676
+ subject: `File asset ${displayAssemblyPath(asset.displayName)}`,
7557
7677
  field: "source.path",
7558
7678
  outdir: resolved.path,
7559
7679
  sink
7560
7680
  });
7561
7681
  return resolved.path;
7562
7682
  }
7563
- if (!resolved.contained) throw new Error(`File asset '${displaySafe(asset.displayName)}' has source.path=${displayAssemblyPath(asset.source.path)} which ${renderAssemblyPathEscape(resolved, assetOutdir, "publish it")}`);
7683
+ if (!resolved.contained) throw new Error(`File asset ${displayAssemblyPath(asset.displayName)} has source.path=${displayAssemblyPath(asset.source.path)} which ${renderAssemblyPathEscape(resolved, assetOutdir, "publish it")}`);
7564
7684
  return resolved.path;
7565
7685
  }
7566
7686
  /**
@@ -7596,7 +7716,7 @@ var AssetManifestLoader = class {
7596
7716
  */
7597
7717
  async loadManifest(cdkOutputDir, stackName) {
7598
7718
  const resolved = resolveAssemblyPath(cdkOutputDir, `${stackName}.assets.json`);
7599
- if (!resolved.contained) throw new Error(`Asset manifest for stack '${displaySafe(stackName)}' ${renderAssemblyPathEscape(resolved, cdkOutputDir)}`);
7719
+ if (!resolved.contained) throw new Error(`Asset manifest for stack ${displayStackName(stackName)} ${renderAssemblyPathEscape(resolved, cdkOutputDir)}`);
7600
7720
  const manifestPath = resolved.path;
7601
7721
  try {
7602
7722
  this.logger.debug(`Loading asset manifest from: ${manifestPath}`);
@@ -7761,7 +7881,7 @@ var FileAssetPublisher = class {
7761
7881
  const destRegion = dest.region ? this.resolvePlaceholders(dest.region, accountId, region) : region;
7762
7882
  this.logger.debug(`Publishing file asset ${asset.displayName || assetHash} → s3://${bucketName}/${objectKey}`);
7763
7883
  const client = new S3Client({
7764
- ...awsClientDefaults(),
7884
+ ...ambientClientDefaults(),
7765
7885
  region: destRegion
7766
7886
  });
7767
7887
  try {
@@ -9559,65 +9679,7 @@ function resolveDockerContextDirectory(opts) {
9559
9679
  }
9560
9680
  /** One spelling of the warning subject, so the two arms cannot drift. */
9561
9681
  function dockerSubject(assetId) {
9562
- return assetId === void 0 ? "A Docker asset" : `Docker asset '${displaySafe(assetId)}'`;
9563
- }
9564
- /**
9565
- * Refuse a Docker asset whose build context the bundled `cdk-local` engine
9566
- * would take from outside the assembly (issue
9567
- * [#3503](https://github.com/go-to-k/cdkd/issues/3503)).
9568
- *
9569
- * Since cdk-local 0.149.3 the engine's own `buildDockerImage` refuses the same
9570
- * escapes and opens the path it judged (go-to-k/cdkd#3597). This copy stays in
9571
- * FRONT of it because the engine's refusal quotes the assembly-chosen value in
9572
- * a boundary the value can close (go-to-k/cdk-local#758), while this one
9573
- * renders it through `displayAssemblyPath`; go-to-k/cdkd#3652 removes it once
9574
- * that ships. It judges the path the engine's `${cdkOutDir}/${source.directory}`
9575
- * spelling names, before the engine runs, through the same
9576
- * {@link resolveDockerContextDirectory} cdkd's own build uses.
9577
- *
9578
- * An ABSOLUTE value is judged by the engine's spelling, not honoured: the
9579
- * concatenation folds it UNDER the manifest directory, so its leading
9580
- * separators are stripped and the remainder takes the relative arm. That
9581
- * keeps a symlink inside the assembly from becoming an escape through an
9582
- * absolute spelling, and keeps the honour-and-warn arm, which describes a
9583
- * build this engine never performs, out of these paths.
9584
- */
9585
- function assertCdkLocalDockerContextContained(opts) {
9586
- const { manifestDir, source, assetOutdir, wrapError } = opts;
9587
- const directory = source.directory;
9588
- if (!directory) return;
9589
- const asEngineJoinsIt = isAbsolute$1(directory) ? directory.replace(/^[/\\]+/, "") : directory;
9590
- resolveDockerContextDirectory({
9591
- manifestDir,
9592
- directory: asEngineJoinsIt,
9593
- wrapError,
9594
- assetOutdir,
9595
- sink: source.executable && source.executable.length > 0 ? "run this asset's source.executable with that directory as its working directory" : "send that directory to docker build as the context of an image cdkd then runs locally"
9596
- });
9597
- const engineSpelled = `${manifestDir}/${asEngineJoinsIt}`;
9598
- const physical = tryRealpathNative(engineSpelled);
9599
- if (physical === void 0) return;
9600
- const bound = tryRealpathNative(assetOutdir) ?? resolve$1(assetOutdir);
9601
- const rel = relative(bound, physical);
9602
- if (rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute$1(rel)) return;
9603
- throw wrapError(`asset source.directory=${displayAssemblyPath(directory)} which ` + renderAssemblyPathEscape({
9604
- contained: false,
9605
- escape: "symlink",
9606
- path: engineSpelled,
9607
- realPath: physical
9608
- }, assetOutdir, "build it"));
9609
- }
9610
- /**
9611
- * `realpath(3)`, or `undefined` when the path does not resolve. `.native` is
9612
- * load-bearing: the JavaScript `realpathSync` folds `..` lexically first,
9613
- * which is the very reading the check above exists to avoid.
9614
- */
9615
- function tryRealpathNative(p) {
9616
- try {
9617
- return realpathSync$1.native(p);
9618
- } catch {
9619
- return;
9620
- }
9682
+ return assetId === void 0 ? "A Docker asset" : `Docker asset ${displayIdent(assetId)}`;
9621
9683
  }
9622
9684
  /**
9623
9685
  * Build a Docker image from a CDK asset source. Returns the local image
@@ -9781,7 +9843,7 @@ var DockerAssetPublisher = class {
9781
9843
  const ecrUri = `${accountId}.dkr.ecr.${destRegion}.${ecrUrlSuffix(destRegion)}/${repositoryName}:${imageTag}`;
9782
9844
  this.logger.debug(`Publishing Docker image ${asset.displayName || assetHash} → ${ecrUri}`);
9783
9845
  const client = new ECRClient({
9784
- ...awsClientDefaults(),
9846
+ ...ambientClientDefaults(),
9785
9847
  region: destRegion
9786
9848
  });
9787
9849
  try {
@@ -9820,7 +9882,7 @@ var DockerAssetPublisher = class {
9820
9882
  const destRegion = dest.region ? this.resolvePlaceholders(dest.region, accountId, region) : region;
9821
9883
  const ecrUri = `${accountId}.dkr.ecr.${destRegion}.${ecrUrlSuffix(destRegion)}/${repositoryName}:${imageTag}`;
9822
9884
  const client = new ECRClient({
9823
- ...awsClientDefaults(),
9885
+ ...ambientClientDefaults(),
9824
9886
  region: destRegion
9825
9887
  });
9826
9888
  try {
@@ -11363,7 +11425,7 @@ var AssetPublisher = class {
11363
11425
  if (!accountId) {
11364
11426
  const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
11365
11427
  const stsClient = new STSClient({
11366
- ...awsClientDefaults(),
11428
+ ...ambientClientDefaults(),
11367
11429
  region
11368
11430
  });
11369
11431
  accountId = (await stsClient.send(new GetCallerIdentityCommand({}))).Account;
@@ -24507,9 +24569,9 @@ function inspectCommand(stackName, region) {
24507
24569
  * entries FIRST ({@link repairMalformedResourceEntriesForReadOnly}), so the
24508
24570
  * row is reported once, as unreadable, rather than warned about as a preview
24509
24571
  * and then dropped. The object test stays here rather than
24510
- * {@link isReadableResourceEntry} because the deploy path takes this
24511
- * predicate WITHOUT the entry one, and narrowing it would let a typeless
24512
- * object with a torn map reach the comparison unrefused. Likewise a `resources` bag that is not readable at all
24572
+ * {@link isReadableResourceEntry} so this verdict does not depend on the entry
24573
+ * guard running first: narrowing it would let a typeless object with a torn
24574
+ * map through any caller that takes this predicate alone. Likewise a `resources` bag that is not readable at all
24513
24575
  * yields `[]` here rather than ids invented from a string's characters — that
24514
24576
  * class is {@link hasReadableResources}'s to report. What a caller must not do
24515
24577
  * is take only this one.
@@ -25167,13 +25229,10 @@ function malformedResourceEntriesWarning(rawStackName, rawRegion, logicalIds) {
25167
25229
  * `logicalId` is not a string arrives as `''` and renders as the
25168
25230
  * `UNRENDERABLE` stand-in.
25169
25231
  */
25170
- function malformedOrphanRecordsWarning(rawStackName, rawRegion, logicalIds) {
25232
+ function malformedOrphanRecordsWarning(rawStackName, rawRegion, logicalIds, alsoRejectsTornMaps) {
25171
25233
  const stackName = absentIfEmpty(rawStackName);
25172
25234
  const region = absentIfEmpty(rawRegion);
25173
- const named = logicalIds.slice(0, NAMED_UNREADABLE_ENTRIES).map((id) => displayLogicalId(id)).join(", ");
25174
- const rest = logicalIds.length - NAMED_UNREADABLE_ENTRIES;
25175
- const more = rest > 0 ? ` and ${rest} more` : "";
25176
- return `${stackClause(stackName, region)} holds ${logicalIds.length} rollback-orphan record(s) in 'orphans' that cannot be read as resources — ${named}${more} — because they are not objects, or carry no resource type. Continuing WITHOUT them: they are not previewed for adoption. These ids were read from 'orphans'; the 'resources' map carries its own warning when it is damaged too. See the stored values with: ` + inspectCommand(stackName, region);
25235
+ return `${stackClause(stackName, region)} holds ${logicalIds.length} rollback-orphan record(s) in 'orphans' that cannot be read as resources — ${namedOrphanRows(logicalIds)} — because they are not objects, have no string 'logicalId' or share it with another row, or their 'state' is not an object, names no resource type${alsoRejectsTornMaps ? ", has no non-empty string 'physicalId', or holds a 'properties' or 'attributes' map that is not an object" : ` or has no non-empty string 'physicalId'`}. Continuing WITHOUT them: ${alsoRejectsTornMaps ? `they are excluded from the secret scan, so nothing this run does can certify them` : "they are not previewed for adoption, and 'cdkd deploy' refuses the record over them rather than dropping them as this command does"}. These ids were read from 'orphans'; the 'resources' map carries its own warning when it is damaged too. See the stored values with: ` + inspectCommand(stackName, region);
25177
25236
  }
25178
25237
  /**
25179
25238
  * The half the refusal and the warning share: what is wrong and which entries.
@@ -25212,6 +25271,46 @@ function malformedResourceEntriesRefusalMessage(rawStackName, rawRegion, logical
25212
25271
  return `${namedEntriesClause(stackName, region, logicalIds)} This command can WRITE state, so it refuses rather than skipping them: saving over the record would report a clean run for entries nothing could read, and would leave the next command to fail on them with no more to go on. Nothing was locked, read from AWS or written FOR THIS STACK. Inspect it with: ` + inspectCommand(stackName, region);
25213
25272
  }
25214
25273
  /**
25274
+ * For `cdkd deploy`: refuse a record whose `resources` map holds a ROW that is
25275
+ * not a readable resource record (go-to-k/cdkd#3314). TWO call sites, one
25276
+ * verdict. `DeployEngine`'s state load is the one a deploy hits: two walks run
25277
+ * between the load and the diff (the CLI's prefix-migration gate and the
25278
+ * observed-state auto-refresh) and each died on a `null` row with a bare
25279
+ * `TypeError`. `DiffCalculator.calculateDiff` also calls it, beside
25280
+ * {@link refuseMalformedResourceProperties}, because that is where the verdict
25281
+ * is made. Any other caller of the diff that forgets gets the refusal.
25282
+ *
25283
+ * The harm is the diff's verdict, not a save. The diff looks up each template
25284
+ * resource by logical id, so a `null` (or any non-object) row reads as "not in
25285
+ * state" and is planned as a CREATE. That either collides with the live
25286
+ * resource's name or creates a SECOND copy and orphans the first. A row with no
25287
+ * `resourceType` compares unequal to the template's type, so it is planned as a
25288
+ * TYPE CHANGE, which is a replacement.
25289
+ *
25290
+ * A third entry point on the ENTRY class rather than a call to
25291
+ * {@link refuseMalformedResourceEntries}, whose text is false here. That text
25292
+ * says "Nothing was locked", but the deploy holds its lock at both sites. Same
25293
+ * predicate, so the verdict cannot diverge.
25294
+ *
25295
+ * The engine passes its own resolved stack and region. `calculateDiff` holds no
25296
+ * trusted pair and passes neither (see {@link stackClause}).
25297
+ */
25298
+ function refuseMalformedResourceEntriesForDeploy(state, stackName, region) {
25299
+ const unreadable = unreadableResourceEntries(state);
25300
+ if (unreadable.length === 0) return;
25301
+ throw markNonRetryable(new CdkdError(malformedDeployResourceEntriesRefusalMessage(stackName, region, unreadable), STATE_RESOURCES_MALFORMED));
25302
+ }
25303
+ /**
25304
+ * The text {@link refuseMalformedResourceEntriesForDeploy} raises. Like
25305
+ * {@link malformedResourcePropertiesRefusalMessage}, it must be true under
25306
+ * `--dry-run` too, since the dry-run return comes after the diff.
25307
+ */
25308
+ function malformedDeployResourceEntriesRefusalMessage(rawStackName, rawRegion, logicalIds) {
25309
+ const stackName = absentIfEmpty(rawStackName);
25310
+ const region = absentIfEmpty(rawRegion);
25311
+ return `${namedEntriesClause(stackName, region, logicalIds)} 'cdkd deploy' can WRITE state and AWS resources, so it refuses rather than continuing — under '--dry-run' too, because the plan a dry run would print is the wrong one: a row that is not an object reads as a resource cdkd does not manage, so the template's resource of that id is planned as a CREATE (a second copy of a live resource, or a name collision), and a row with no resource type is planned as a TYPE CHANGE, which replaces the live resource. Nothing was provisioned and no state was written FOR THIS STACK. Repair or remove the record first; 'cdkd diff' previews the rest of the stack without those rows and warns that it did. Inspect the record with: ` + inspectCommand(stackName, region);
25312
+ }
25313
+ /**
25215
25314
  * For `cdkd orphan`: refuse a record whose SURVIVING `resources` entries are
25216
25315
  * not readable resource records (go-to-k/cdkd#3350).
25217
25316
  *
@@ -25305,12 +25404,177 @@ function malformedOrphanResourceAttributesRefusalMessage(rawStackName, rawRegion
25305
25404
  return orphanRefusal(stackName, region, recovery, `${stackClause(stackName, region)} holds ${logicalIds.length} resource record(s) whose 'attributes' map cannot be read — ${named}${more} — because it is null or not an object. The record is malformed or truncated. 'cdkd orphan' REWRITES and SAVES every record it keeps, so it refuses rather than continuing — under '--dry-run' too, so a dry run never prints a plan the real run refuses. The map is carried into the save VERBATIM, and a list is walked, so its rewrites are recorded into a container that is still not a map. It is the cache a later 'cdkd deploy' reads another resource's 'Fn::GetAtt' of this one from, so continuing would report success over the one map that read depends on.`, SURVIVOR_THIRD_WAY_OUT);
25306
25405
  }
25307
25406
  /**
25407
+ * Is ONE `orphans[]` record PREVIEWABLE — the narrow half of
25408
+ * {@link isReadableOrphanRecord}, for `cdkd diff` alone (go-to-k/cdkd#3500).
25409
+ *
25410
+ * An object, a string `logicalId`, and a `state` that is a readable resource
25411
+ * entry with a non-empty string `physicalId`: everything the adoption preview
25412
+ * dereferences, and nothing more. The `physicalId` half is NOT inherited from
25413
+ * {@link isReadableResourceEntry} — see {@link hasStringPhysicalId} for why that
25414
+ * one stops at `resourceType` and why a ROW cannot. It
25415
+ * deliberately does not ask about that entry's `properties`, because `cdkd diff`
25416
+ * has a LATER answer for that one — `computeStackDiff` runs the `properties`
25417
+ * repair a second time over the adopted records and names what it emptied, which
25418
+ * `docs/cli-diff.md` and `.claude/rules/state-malformed-properties.md` both
25419
+ * describe. Dropping such a row here instead would make that repair unreachable
25420
+ * and silently retire a documented report. A torn `attributes` map rides through
25421
+ * for the same reason and gets NO later answer: stated rather than solved, and
25422
+ * the same residual `cdkd diff` had before this predicate existed.
25423
+ *
25424
+ * It CANNOT see rows sharing a `logicalId` (go-to-k/cdkd#3643), a LIST-level
25425
+ * defect — never filter a list with it alone; take
25426
+ * {@link previewableOrphanRecords} / {@link unpreviewableOrphanRecords}.
25427
+ *
25428
+ * Every WRITER takes the full predicate: none of them has a second answer, and
25429
+ * a torn `properties` map reaching a save is go-to-k/cdkd#3344's defect.
25430
+ */
25431
+ function isPreviewableOrphanRecord(record) {
25432
+ if (!isReadableBag(record)) return false;
25433
+ if (typeof record.logicalId !== "string") return false;
25434
+ const entry = record.state;
25435
+ return isReadableResourceEntry(entry) && hasStringPhysicalId(entry);
25436
+ }
25437
+ /**
25438
+ * Does this resource entry carry a NON-EMPTY string `physicalId`?
25439
+ *
25440
+ * Its own helper rather than a clause spelled in each row predicate, so the two
25441
+ * cannot drift on it, and deliberately NOT folded into
25442
+ * {@link isReadableResourceEntry}: that one answers for a `resources` ENTRY,
25443
+ * where stopping at `resourceType` is a recorded decision — a row with a type and
25444
+ * no physical id fails per-resource where it is used and is reported by that
25445
+ * command in its own terms.
25446
+ *
25447
+ * An `orphans` ROW has no such second report, which is why it asks for more
25448
+ * (go-to-k/cdkd#3641 review). The record names a resource cdkd deliberately LEFT
25449
+ * LIVE in AWS, so the physical id is the only handle on it: `cdkd destroy`'s
25450
+ * pre-confirmation listing is the operator's only notice before the record is
25451
+ * deleted, and it rendered an absent id as an EMPTY field in the row they
25452
+ * approve. `planOrphanAdoption` dereferences it eight times over — including
25453
+ * `knownPhysicalId` on the provider call and `claims().has(...)` for a
25454
+ * cross-stack collision — so a row without one decides an adoption from
25455
+ * `undefined`.
25456
+ */
25457
+ function hasStringPhysicalId(entry) {
25458
+ const physicalId = entry.physicalId;
25459
+ return typeof physicalId === "string" && physicalId !== "";
25460
+ }
25461
+ /**
25462
+ * The `orphans[]` records {@link isPreviewableOrphanRecord} rejects, PLUS every
25463
+ * row sharing its string `logicalId` with another row (go-to-k/cdkd#3643),
25464
+ * named the way {@link unreadableOrphanRecords} names its own.
25465
+ */
25466
+ function unpreviewableOrphanRecords(state) {
25467
+ if (!Array.isArray(state.orphans)) return [];
25468
+ const rows = state.orphans;
25469
+ const shared = sharedOrphanLogicalIds(rows);
25470
+ return rows.filter((record) => !isPreviewableOrphanRecord(record) || sharesLogicalId(record, shared)).map(orphanRowName);
25471
+ }
25472
+ /**
25473
+ * The rows `cdkd diff` KEEPS for the adoption preview: the complement of
25474
+ * {@link unpreviewableOrphanRecords}, homed beside it so the two cannot
25475
+ * disagree about a row (go-to-k/cdkd#3643). `[]` for a container that is not a
25476
+ * list, which {@link hasReadableOrphans} reports.
25477
+ *
25478
+ * Why not `orphans.filter(isPreviewableOrphanRecord)` at the call site, which is
25479
+ * what `cdkd diff` did: that predicate decides ONE record, and a shared
25480
+ * `logicalId` is a property of the LIST. Two rows carrying one id both pass it,
25481
+ * and `planOrphanAdoption` then keys its adoption map on that id, so the preview
25482
+ * shows ONE adoption for two rows — the collapse the writers now refuse.
25483
+ */
25484
+ function previewableOrphanRecords(state) {
25485
+ if (!Array.isArray(state.orphans)) return [];
25486
+ const rows = state.orphans;
25487
+ const shared = sharedOrphanLogicalIds(rows);
25488
+ return rows.filter((record) => isPreviewableOrphanRecord(record) && !sharesLogicalId(record, shared));
25489
+ }
25490
+ /**
25491
+ * The string `logicalId`s that TWO OR MORE rows of `orphans` carry
25492
+ * (go-to-k/cdkd#3643) — `''` included, since an empty string is still a key.
25493
+ *
25494
+ * Such a record is MALFORMED, not merely unusual: no supported writer produces
25495
+ * one, because `orphansAfterRollback` merges by `logicalId` and keeps one row per
25496
+ * id. What a reader does with it is the loss the row predicate exists to refuse
25497
+ * for a MISSING id — `orphansAfterRollback` keeps only the LAST of the rows, and
25498
+ * the deploy's adoption pass writes `adopted[logicalId]` for each, so the first
25499
+ * row's resource stays live in AWS with nothing tracking it.
25500
+ *
25501
+ * Counted over EVERY row that is an object carrying a string id, not only the
25502
+ * rows the per-record predicate accepts: a torn row and a healthy one sharing an
25503
+ * id still say the record was edited or damaged, and keeping the healthy one
25504
+ * would have a read-only command preview, as the stack's only row of that id, a
25505
+ * row whose twin the writers refuse over. A `Map` rather than a plain object, so
25506
+ * an id spelled `__proto__` or `constructor` is counted rather than resolved
25507
+ * through the prototype.
25508
+ */
25509
+ function sharedOrphanLogicalIds(rows) {
25510
+ const seen = /* @__PURE__ */ new Map();
25511
+ for (const record of rows) {
25512
+ if (!isReadableBag(record)) continue;
25513
+ const id = record.logicalId;
25514
+ if (typeof id !== "string") continue;
25515
+ seen.set(id, (seen.get(id) ?? 0) + 1);
25516
+ }
25517
+ const shared = /* @__PURE__ */ new Set();
25518
+ for (const [id, count] of seen) if (count > 1) shared.add(id);
25519
+ return shared;
25520
+ }
25521
+ /** Does this row carry one of the {@link sharedOrphanLogicalIds}? */
25522
+ function sharesLogicalId(record, shared) {
25523
+ if (shared.size === 0 || !isReadableBag(record)) return false;
25524
+ const id = record.logicalId;
25525
+ return typeof id === "string" && shared.has(id);
25526
+ }
25527
+ /**
25528
+ * How every `orphans` ROW helper names a row: its `logicalId`, or `''`
25529
+ * (rendered as the `UNRENDERABLE` stand-in) when it has no string one.
25530
+ */
25531
+ function orphanRowName(record) {
25532
+ const id = isReadableBag(record) ? record.logicalId : void 0;
25533
+ return typeof id === "string" ? id : "";
25534
+ }
25535
+ /**
25536
+ * Is ONE `orphans[]` record usable by a reader (go-to-k/cdkd#3500)?
25537
+ *
25538
+ * Split out of {@link unreadableOrphanRecords} rather than written a second
25539
+ * time, so the five WRITE-capable readers that take a row's fields off the cast
25540
+ * and the `cdkd orphan` refusal that predates them cannot disagree about which
25541
+ * rows are usable. `cdkd diff` deliberately disagrees for one class — see
25542
+ * {@link isPreviewableOrphanRecord}, which is the narrower half it takes. The two shapes answer different questions and share this verdict:
25543
+ * this one decides ONE record, that one names every record a state fails on.
25544
+ *
25545
+ * A reader needs all of it to hold, because it reaches the record as a WHOLE:
25546
+ * the record is an object, its `logicalId` is a string (a row with none keys
25547
+ * `orphansAfterRollback`'s merge map on `undefined`, so several of them collapse
25548
+ * into one), its `state` is a readable resource entry carrying a NON-EMPTY string
25549
+ * `physicalId` ({@link hasStringPhysicalId} carries why that is asked here and
25550
+ * not for a `resources` entry), and that entry's `properties` and `attributes`
25551
+ * maps are readable.
25552
+ *
25553
+ * It CANNOT see the one LIST-level defect: two rows sharing a string `logicalId`
25554
+ * collapse in the same merge map, and each of them passes this test
25555
+ * (go-to-k/cdkd#3643). So never filter a list with it alone — take
25556
+ * {@link unreadableOrphanRecords} (or, for `cdkd diff`,
25557
+ * {@link previewableOrphanRecords}), which add that check.
25558
+ */
25559
+ function isReadableOrphanRecord(record) {
25560
+ if (!isPreviewableOrphanRecord(record)) return false;
25561
+ const entry = record.state;
25562
+ return isReadableBag(entry.properties) && hasReadableAttributes(entry);
25563
+ }
25564
+ /**
25308
25565
  * The `orphans[]` records a reader cannot use: a record that is not an object
25309
- * or has no string `logicalId`, whose `state` is not a readable resource entry,
25310
- * or whose `state` carries an unreadable `properties` or `attributes` map
25566
+ * or has no string `logicalId`, or whose `state` is not a readable resource entry,
25567
+ * carries no non-empty string `physicalId`, or carries an unreadable `properties`
25568
+ * or `attributes` map
25311
25569
  * (go-to-k/cdkd#3344). The `logicalId` half is go-to-k/cdkd#3500's row
25312
- * predicate: rows with none all key one map entry, and `orphansAfterRollback`
25313
- * collapses them into one.
25570
+ * predicate: rows MISSING one all key the same map entry, and
25571
+ * `orphansAfterRollback` collapses those into one. EVERY row whose string
25572
+ * `logicalId` another row also carries is named too — each of them, since
25573
+ * nothing says which is the live resource (go-to-k/cdkd#3643,
25574
+ * {@link sharedOrphanLogicalIds}): those collapse in the same map by the same
25575
+ * mechanism, and it is the check {@link isReadableOrphanRecord} cannot make.
25576
+ * Folded in HERE so every refusal, the read-only repair and `cdkd orphan`
25577
+ * inherit it and no caller re-spells it.
25314
25578
  *
25315
25579
  * Every `ResourceState` question the `resources` side answers in separate
25316
25580
  * predicates, asked in ONE here: a rollback-orphan record is reached only as a
@@ -25323,15 +25587,100 @@ function malformedOrphanResourceAttributesRefusalMessage(rawStackName, rawRegion
25323
25587
  */
25324
25588
  function unreadableOrphanRecords(state) {
25325
25589
  if (!Array.isArray(state.orphans)) return [];
25326
- return state.orphans.filter((record) => {
25327
- if (!isReadableBag(record)) return true;
25328
- if (typeof record.logicalId !== "string") return true;
25329
- const entry = record.state;
25330
- return !isReadableResourceEntry(entry) || !isReadableBag(entry.properties) || !hasReadableAttributes(entry);
25331
- }).map((record) => {
25332
- const id = isReadableBag(record) ? record.logicalId : void 0;
25333
- return typeof id === "string" ? id : "";
25334
- });
25590
+ const rows = state.orphans;
25591
+ const shared = sharedOrphanLogicalIds(rows);
25592
+ return rows.filter((record) => isUnusableOrphanRow(record, shared)).map(orphanRowName);
25593
+ }
25594
+ /**
25595
+ * The writers' per-row verdict with the list-level half applied: the ONE test
25596
+ * {@link unreadableOrphanRecords} names by and
25597
+ * {@link repairMalformedOrphanRecordsForReadOnly} drops by, so the rows a
25598
+ * dry run drops are exactly the rows the real run refuses over.
25599
+ */
25600
+ function isUnusableOrphanRow(record, shared) {
25601
+ return !isReadableOrphanRecord(record) || sharesLogicalId(record, shared);
25602
+ }
25603
+ /**
25604
+ * Refuse a readable `orphans` list holding a record no reader can use, on a
25605
+ * path that can WRITE state (go-to-k/cdkd#3500).
25606
+ *
25607
+ * The CONTAINER pair beside this one ({@link refuseMalformedOrphans} and its
25608
+ * destroy twin) answers "is the field a list"; this answers "is every row in it
25609
+ * usable", and a list can fail the second while passing the first. One
25610
+ * predicate for both questions would be wrong in the direction that loses
25611
+ * evidence: `cdkd diff` repairs the container and must DROP a row, which is not
25612
+ * the same verdict.
25613
+ *
25614
+ * Marked non-retryable for {@link refuseMalformedOrphans}'s reason: a retry
25615
+ * cannot change a persisted record, and a nested child's deploy runs inside a
25616
+ * `withRetry`.
25617
+ *
25618
+ * It takes NO `LockRecoveryContext`, matching its container twin
25619
+ * {@link refuseMalformedOrphans}: no caller on these paths holds one, and a
25620
+ * parameter every call site omits renders nothing. The message builder keeps its
25621
+ * own `recovery` parameter, and its arms are pinned by calling it directly —
25622
+ * `refuseMalformedOrphansForOrphan` is NOT that route, since it raises a
25623
+ * different text ({@link malformedOrphansForOrphanRefusalMessage}).
25624
+ */
25625
+ function refuseMalformedOrphanRecords(state, stackName, region) {
25626
+ const unreadable = unreadableOrphanRecords(state);
25627
+ if (unreadable.length === 0) return;
25628
+ throw markNonRetryable(new CdkdError(malformedOrphanRecordsRefusalMessage(stackName, region, unreadable), STATE_RESOURCES_MALFORMED));
25629
+ }
25630
+ /**
25631
+ * The DESTROY twin of {@link refuseMalformedOrphanRecords}, carrying its own
25632
+ * text for the reason the container pair splits: a destroy neither walks the
25633
+ * record back into `resources` nor writes a reshaped one, so the writer text's
25634
+ * mechanism would be false here. What it does instead is REPORT the rows — the
25635
+ * operator's only notice that those resources stop being tracked — from fields
25636
+ * it never validates, and then remove the record.
25637
+ */
25638
+ function refuseMalformedOrphanRecordsForDestroy(state, stackName, region) {
25639
+ const unreadable = unreadableOrphanRecords(state);
25640
+ if (unreadable.length === 0) return;
25641
+ throw markNonRetryable(new CdkdError(malformedOrphanRecordsForDestroyRefusalMessage(stackName, region, unreadable), STATE_RESOURCES_MALFORMED));
25642
+ }
25643
+ /**
25644
+ * The WRITER text {@link refuseMalformedOrphanRecords} raises.
25645
+ *
25646
+ * Its mechanism clause names THREE outcomes rather than one, because a reader
25647
+ * meets a different one per shape: a row MISSING its `logicalId` keys
25648
+ * `orphansAfterRollback`'s merge map on `undefined`, so rows missing it collapse
25649
+ * to one saved survivor (distinct NUMERIC ids do not — they stay distinct keys),
25650
+ * and rows SHARING a string one collapse the same way (go-to-k/cdkd#3643);
25651
+ * a row whose `state` is absent or null aborts wherever it is first
25652
+ * dereferenced; and a `state` that is a primitive or names no resource type
25653
+ * reads as `undefined` and is carried or reported as though it were a record.
25654
+ */
25655
+ function malformedOrphanRecordsRefusalMessage(rawStackName, rawRegion, logicalIds, recovery) {
25656
+ const stackName = absentIfEmpty(rawStackName);
25657
+ const region = absentIfEmpty(rawRegion);
25658
+ return orphanRefusal(stackName, region, recovery, `${stackClause(stackName, region)} holds ${logicalIds.length} rollback-orphan record(s) in 'orphans' that cannot be read — ${namedOrphanRows(logicalIds)} — because the record has no string 'logicalId' or shares it with another row, or its 'state' is not an object, names no resource type, has no non-empty string 'physicalId', or holds a 'properties' or 'attributes' map that is not an object. This command can WRITE state, and what it would do with such a row is not one thing: 'cdkd rollback' merges by 'logicalId', so rows MISSING one, or SHARING one, key the same entry and only one of them survives into the record it saves, the others' resources left live in AWS with nothing tracking them; a row whose 'state' is absent or null aborts wherever it is first dereferenced, with an error naming neither the field nor the stack; and the quietest shape is neither — a 'state' that is a primitive, or an object naming no resource type, reads as 'undefined' and is carried or reported as if it were a record. It refuses rather than pick one of those outcomes.`, "Repair the record by hand rather than deleting it — it is the only record that an earlier failed deploy left its resource live in AWS.", ORPHANS_DROP_CAVEAT);
25659
+ }
25660
+ /** The DESTROY text {@link refuseMalformedOrphanRecordsForDestroy} raises. */
25661
+ function malformedOrphanRecordsForDestroyRefusalMessage(rawStackName, rawRegion, logicalIds, recovery) {
25662
+ const stackName = absentIfEmpty(rawStackName);
25663
+ const region = absentIfEmpty(rawRegion);
25664
+ return orphanRefusal(stackName, region, recovery, `${stackClause(stackName, region)} holds ${logicalIds.length} rollback-orphan record(s) in 'orphans' that cannot be read — ${namedOrphanRows(logicalIds)} — because the record has no string 'logicalId' or shares it with another row, or its 'state' is not an object, names no resource type, has no non-empty string 'physicalId', or holds a 'properties' or 'attributes' map that is not an object. This command DELETES state, and the record is the only evidence that those resources are live in AWS — so it refuses over any part of a row it cannot read rather than removing evidence it could not fully report. What the DAMAGE costs the report depends on which part is torn: the listing that tells you which resources stop being tracked prints the row's own 'logicalId', resource type and physical id, from fields nothing else validates, so it can abort before the confirmation naming nothing, or print a row with a field missing and let you approve the delete. A torn 'properties' or 'attributes' map the listing never reads is refused too, because what is discarded is the whole record. So are rows sharing one 'logicalId', though the listing would print each: no cdkd command writes such a record — the rollback save merges by id and every other save carries the list unchanged — so it was damaged or edited by hand, and deleting it would discard it before anyone decides which of those resources the stack still owns.`, "Repair the record by hand rather than deleting it — it is the only record that an earlier failed deploy left its resource live in AWS.", ORPHANS_DROP_CAVEAT);
25665
+ }
25666
+ /**
25667
+ * The READ-ONLY disposition for the same rows: DROP them and return their
25668
+ * names, for a command that never writes the record back — `cdkd scrub
25669
+ * --dry-run` today.
25670
+ *
25671
+ * Returns the dropped names rather than a boolean because its caller reports
25672
+ * them: `cdkd scrub --dry-run` warns with them by name, and the stack joins the
25673
+ * audited-record refusal's list. `cdkd diff` does NOT call this — it needs the
25674
+ * narrower {@link isPreviewableOrphanRecord} and keeps the survivors itself. An
25675
+ * empty return means nothing was dropped and the list is untouched.
25676
+ */
25677
+ function repairMalformedOrphanRecordsForReadOnly(state) {
25678
+ const unreadable = unreadableOrphanRecords(state);
25679
+ if (unreadable.length === 0) return [];
25680
+ const rows = state.orphans ?? [];
25681
+ const shared = sharedOrphanLogicalIds(rows);
25682
+ state.orphans = rows.filter((record) => !isUnusableOrphanRow(record, shared));
25683
+ return unreadable;
25335
25684
  }
25336
25685
  /**
25337
25686
  * For `cdkd orphan`: refuse a record whose `orphans` list, or any record in
@@ -25355,6 +25704,87 @@ function refuseMalformedOrphansForOrphan(state, stackName, region, recovery) {
25355
25704
  if (unreadable.length === 0) return;
25356
25705
  throw new CdkdError(malformedOrphansForOrphanRefusalMessage(stackName, region, unreadable, recovery), STATE_RESOURCES_MALFORMED);
25357
25706
  }
25707
+ /**
25708
+ * The `--fail` / exit-3 reason `cdkd diff` reports for an `orphans` row it KEEPS
25709
+ * in the preview and `cdkd deploy` REFUSES (go-to-k/cdkd#3641 maintainer review,
25710
+ * M1).
25711
+ *
25712
+ * The two predicates disagree on exactly one class — a row whose `properties` or
25713
+ * `attributes` map is torn — and that disagreement is deliberate: `cdkd diff`
25714
+ * keeps such a row so the second `properties` repair can name it. What was
25715
+ * missing is the OTHER half of keeping it: the deploy refuses the whole record
25716
+ * over that same row, so a preview that says nothing lets `cdkd diff --fail`
25717
+ * exit 0 and the deploy the operator runs next refuse. That is the contract
25718
+ * `cdkd diff` states for the adoption preview, and this PR is what made it
25719
+ * false. Precisely which shapes: a torn `attributes` map, and a torn `properties`
25720
+ * map on a row the adoption did NOT take — an ADOPTED torn-`properties` row was
25721
+ * already refused at deploy through `calculateDiff` before this lane
25722
+ * (go-to-k/cdkd#3641 item o5), and it is the one the sibling arm reports.
25723
+ *
25724
+ * Homed HERE rather than in `diff-recursive.ts` for the reason every text in
25725
+ * this module is: it renders untrusted logical ids, so it needs
25726
+ * {@link namedOrphanRows} — the same cap, the same `displayLogicalId`, the same
25727
+ * `UNRENDERABLE` stand-in. It NAMES the rows as well as counting them, unlike
25728
+ * {@link deployRefusesPropertiesReason}'s count-only line in that file. It keeps
25729
+ * the names even though {@link malformedOrphanRowsKeptWarning} now names them
25730
+ * too: this reason ships in the `--json` payload, where "see the warning above"
25731
+ * has no referent — the same argument that sibling's doc makes for its own
25732
+ * shape.
25733
+ */
25734
+ function deployRefusesOrphanRowsReason(logicalIds) {
25735
+ return `${logicalIds.length} rollback-orphan record(s) in 'orphans' — ${namedOrphanRows(logicalIds)} — carry a 'properties' or 'attributes' map that cannot be read. This preview KEEPS them, so any adoption shown for them is one 'cdkd deploy' will not perform: it refuses the record over the same rows, and the deploy this previews will not start.`;
25736
+ }
25737
+ /**
25738
+ * The WARNING `cdkd diff` prints for an `orphans` row it KEEPS and a writer
25739
+ * refuses (go-to-k/cdkd#3641 maintainer round 2, M1 continued).
25740
+ *
25741
+ * At every node the RUN REACHES with an adoption preview, which is narrower than
25742
+ * "every node" and the difference is not pedantry (round 4): a non-recursive run —
25743
+ * the default — returns before visiting any child, a state-only child being
25744
+ * DELETED is built with no `previewOrphanAdoption` so the whole row pass is
25745
+ * skipped, and any other caller omitting that option skips it too.
25746
+ *
25747
+ * The exit-3 reason beside this one is TOP-LEVEL only, which is
25748
+ * go-to-k/cdkd#3335's scope decision and right: the deploy skips an unchanged
25749
+ * nested-stack row, so a reason there would report a refusal over a deploy that
25750
+ * succeeds. What made that split SAFE for every other class is that each one
25751
+ * still warns at every node — and this class had no warning at all, because the
25752
+ * row is KEPT and {@link malformedOrphanRecordsWarning} speaks only for rows that
25753
+ * were DROPPED. So a changed nested child holding such a row printed nothing and
25754
+ * its deploy refused.
25755
+ *
25756
+ * It says what the row IS and what the deploy will do, and makes no claim about
25757
+ * what else happens to the row — which is where the first two spellings went
25758
+ * wrong (go-to-k/cdkd#3641 rounds 3 and 4). One said the map "is repaired and
25759
+ * named again over the records an adoption takes", false for every row that
25760
+ * survives the caller's subtraction; the next said nothing else repairs or
25761
+ * previews these rows, false for an ADOPTED row whose `properties` are healthy and
25762
+ * whose `attributes` are torn — the subtraction removes adopted torn-`properties`
25763
+ * names, not every adopted row, and `planOrphanAdoption` rebuilds that row's
25764
+ * attributes and previews the adopted resource. The row's disposition beyond
25765
+ * "kept" is the caller's business, not this sentence's.
25766
+ *
25767
+ * It says the deploy of THIS stack refuses, not that "the deploy this previews"
25768
+ * will not start (round 4 again): under `--recursive` this warning can name a row
25769
+ * in an UNCHANGED nested child, whose parent deploy skips that child and
25770
+ * succeeds. The stack the message already names is the one whose deploy refuses.
25771
+ */
25772
+ function malformedOrphanRowsKeptWarning(rawStackName, rawRegion, logicalIds) {
25773
+ const stackName = absentIfEmpty(rawStackName);
25774
+ const region = absentIfEmpty(rawRegion);
25775
+ return `${stackClause(stackName, region)} holds ${logicalIds.length} rollback-orphan record(s) in 'orphans' — ${namedOrphanRows(logicalIds)} — whose 'properties' or 'attributes' map is not an object. This preview KEEPS them. 'cdkd deploy' of THIS stack does NOT: it refuses the whole record over these rows and will not start until they are repaired. See the stored values with: ` + inspectCommand(stackName, region);
25776
+ }
25777
+ /**
25778
+ * The `<ids> and N more` fragment every `orphans` ROW text shares — the three
25779
+ * refusals and the warning — so a fifth cannot spell the cap differently
25780
+ * (go-to-k/cdkd#3500). Derive the list with
25781
+ * `grep -n "namedOrphanRows(" src/state/malformed-resources-bag.ts`.
25782
+ */
25783
+ function namedOrphanRows(logicalIds) {
25784
+ const named = logicalIds.slice(0, NAMED_UNREADABLE_ENTRIES).map((id) => displayLogicalId(id)).join(", ");
25785
+ const rest = logicalIds.length - NAMED_UNREADABLE_ENTRIES;
25786
+ return `${named}${rest > 0 ? ` and ${rest} more` : ""}`;
25787
+ }
25358
25788
  /** See {@link orphanRefusal}'s `dropCaveat`. */
25359
25789
  const ORPHANS_DROP_CAVEAT = "; it also discards the 'orphans' list, the only record of resources an earlier failed deploy left live in AWS";
25360
25790
  /**
@@ -25371,10 +25801,7 @@ function malformedOrphansForOrphanRefusalMessage(rawStackName, rawRegion, logica
25371
25801
  const region = absentIfEmpty(rawRegion);
25372
25802
  const verbatim = "'cdkd orphan' carries it into the record it saves VERBATIM, without reading it, so it refuses rather than continuing — under '--dry-run' too, so a dry run never prints a plan the real run refuses.";
25373
25803
  if (logicalIds === void 0) return orphanRefusal(stackName, region, recovery, `${stackClause(stackName, region)} has no readable 'orphans' list — the record is malformed or truncated. ${verbatim} The next 'cdkd deploy' refuses the same record, so continuing would only report success over it and leave that refusal one command later.`, "'cdkd orphan' cannot address this list at all, and rewriting it to [] by hand discards the only record of resources an earlier failed deploy left live in AWS.", ORPHANS_DROP_CAVEAT);
25374
- const named = logicalIds.slice(0, NAMED_UNREADABLE_ENTRIES).map((id) => displayLogicalId(id)).join(", ");
25375
- const rest = logicalIds.length - NAMED_UNREADABLE_ENTRIES;
25376
- const more = rest > 0 ? ` and ${rest} more` : "";
25377
- return orphanRefusal(stackName, region, recovery, `${stackClause(stackName, region)} holds ${logicalIds.length} rollback-orphan record(s) in 'orphans' that cannot be read — ${named}${more} — because the record has no string 'logicalId', its 'state' is not an object or names no resource type, or that state's 'properties' or 'attributes' map is not one. The record is malformed or truncated. ${verbatim} Those records are not inert: they are what the next 'cdkd deploy' may re-adopt into 'resources' and what 'cdkd diff' previews for adoption, so continuing would report success over damage the next command meets.`, "'cdkd orphan' cannot remove such a record itself: it is not in 'resources', so it has no construct path. Repair the record by hand rather than deleting it — it is the only record that an earlier failed deploy left its resource live in AWS.", ORPHANS_DROP_CAVEAT);
25804
+ return orphanRefusal(stackName, region, recovery, `${stackClause(stackName, region)} holds ${logicalIds.length} rollback-orphan record(s) in 'orphans' that cannot be read — ${namedOrphanRows(logicalIds)} — because the record has no string 'logicalId' or shares it with another row, or its 'state' is not an object, names no resource type, has no non-empty string 'physicalId', or holds a 'properties' or 'attributes' map that is not an object. The record is malformed or truncated. ${verbatim} Those records are not inert: they are what the next 'cdkd deploy' may re-adopt into 'resources' and what 'cdkd diff' previews for adoption, so continuing would report success over damage the next command meets.`, "'cdkd orphan' cannot remove such a record itself: it is not in 'resources', so it has no construct path. Repair the record by hand rather than deleting it — it is the only record that an earlier failed deploy left its resource live in AWS.", ORPHANS_DROP_CAVEAT);
25378
25805
  }
25379
25806
 
25380
25807
  //#endregion
@@ -25429,6 +25856,21 @@ const INTRINSIC_KEYS = /* @__PURE__ */ new Set([
25429
25856
  */
25430
25857
  const IN_PLACE_UPDATE_DERIVED_ATTRS = Object.freeze({ "AWS::EC2::LaunchTemplate": /* @__PURE__ */ new Set(["LatestVersionNumber", "DefaultVersionNumber"]) });
25431
25858
  /**
25859
+ * The PREFIX twin of {@link IN_PLACE_UPDATE_DERIVED_ATTRS}, for a type whose
25860
+ * derived attributes are an open family rather than a fixed list (issue
25861
+ * [#3631](https://github.com/go-to-k/cdkd/issues/3631)).
25862
+ *
25863
+ * A nested stack's `Outputs.<Key>` attributes are the child's outputs, and a
25864
+ * child-only change moves them while the parent's `AWS::CloudFormation::Stack`
25865
+ * row changes only `TemplateURL` / `Parameters`. The parent's diff runs BEFORE
25866
+ * the child deploys, so a reader of `Fn::GetAtt [Child, 'Outputs.<Key>']`
25867
+ * resolves against the PREVIOUS value and diffs NO_CHANGE. Which outputs move
25868
+ * is decided by the child's own deploy, so it is not knowable here: every
25869
+ * reader is promoted, and the deploy engine's re-resolve-and-skip drops the
25870
+ * ones whose output did not move.
25871
+ */
25872
+ const IN_PLACE_UPDATE_DERIVED_ATTR_PREFIXES = Object.freeze({ "AWS::CloudFormation::Stack": Object.freeze(["Outputs."]) });
25873
+ /**
25432
25874
  * Diff calculator for comparing desired state (template) with current state
25433
25875
  */
25434
25876
  var DiffCalculator = class DiffCalculator {
@@ -25464,6 +25906,7 @@ var DiffCalculator = class DiffCalculator {
25464
25906
  */
25465
25907
  async calculateDiff(currentState, desiredTemplate, resolveFn, canonicalizeProperties, allowedUnsupportedProperties) {
25466
25908
  const changes = /* @__PURE__ */ new Map();
25909
+ refuseMalformedResourceEntriesForDeploy(currentState, void 0, void 0);
25467
25910
  refuseMalformedResourceProperties(currentState, void 0, void 0);
25468
25911
  const currentResources = currentState.resources;
25469
25912
  const desiredResources = desiredTemplate.Resources;
@@ -25558,8 +26001,11 @@ var DiffCalculator = class DiffCalculator {
25558
26001
  });
25559
26002
  this.logger.debug(`DELETE: ${logicalId} (${currentResource.resourceType})`);
25560
26003
  }
25561
- this.promoteReplacementDependents(changes, desiredTemplate);
25562
- this.promoteInPlaceAttributeDependents(changes, desiredTemplate, rawGetAttRefs);
26004
+ let round = 0;
26005
+ do {
26006
+ round++;
26007
+ this.promoteReplacementDependents(changes, desiredTemplate);
26008
+ } while (this.promoteInPlaceAttributeDependents(changes, desiredTemplate, rawGetAttRefs, currentResources, round));
25563
26009
  const summary = this.getSummary(changes);
25564
26010
  this.logger.debug(`Diff calculated: ${summary.create} CREATE, ${summary.update} UPDATE, ${summary.delete} DELETE, ${summary.noChange} NO_CHANGE`);
25565
26011
  return changes;
@@ -25671,7 +26117,11 @@ var DiffCalculator = class DiffCalculator {
25671
26117
  * update side-effects even though it is not a template property — the
25672
26118
  * per-type {@link IN_PLACE_UPDATE_DERIVED_ATTRS} allow list (issue #985;
25673
26119
  * e.g. `AWS::EC2::LaunchTemplate.LatestVersionNumber`, which an
25674
- * `autoscaling.AutoScalingGroup` reads for its `LaunchTemplate.Version`).
26120
+ * `autoscaling.AutoScalingGroup` reads for its `LaunchTemplate.Version`);
26121
+ * or
26122
+ * 3. `Attr` starts with a derived-attribute PREFIX of `Up`'s type — the
26123
+ * {@link IN_PLACE_UPDATE_DERIVED_ATTR_PREFIXES} table (issue #3631; a
26124
+ * nested stack's `Outputs.<Key>`).
25675
26125
  * (`Ref` resolves to the physical id, which an in-place update never moves, so
25676
26126
  * Ref-only dependents are left NO_CHANGE; likewise a GetAtt of a computed
25677
26127
  * attribute NOT in the allow list, e.g. a Lambda `Arn` on a Description edit.)
@@ -25680,14 +26130,25 @@ var DiffCalculator = class DiffCalculator {
25680
26130
  * promoted resource against the in-flight state and skips the provider call
25681
26131
  * when nothing actually changed.
25682
26132
  *
25683
- * Single-hop by design (unlike {@link promoteReplacementDependents}, which is
25684
- * fully transitive via a worklist): `changedPropsByUpstream` is frozen at entry,
25685
- * so a chain `A(in-place) -> B(reads A's changed attr) -> C(reads B's now-changed
25686
- * attr)` promotes B but not C. Deep GetAtt-of-a-changed-attr chains are rare in
25687
- * practice; if one ever needs full transitivity, promote to a worklist that
25688
- * re-derives changed props as dependents are promoted.
25689
- */
25690
- promoteInPlaceAttributeDependents(changes, desiredTemplate, rawGetAttRefs) {
26133
+ * One pass is single-hop: `changedPropsByUpstream` is frozen at entry, so a
26134
+ * chain `A(in-place) -> B(reads A's changed attr) -> C(reads B's now-changed
26135
+ * attr)` promotes B but not C. `calculateDiff` re-runs it to a fixpoint, and
26136
+ * the pass after B's promotion sees B as an in-place UPDATE.
26137
+ *
26138
+ * A SPECULATIVE promotion — arm 3 in any round, and every arm after the first
26139
+ * round, whose upstreams exist only because an earlier promotion guessed —
26140
+ * skips a dependent that reads a MASKED attribute anywhere
26141
+ * ({@link readsMaskedAttribute}). The engine resolves the dependent's whole
26142
+ * bag and refuses a read of `***` BEFORE its no-change skip, so such a guess
26143
+ * would fail a deploy that left the dependent alone before this pass could
26144
+ * reach it (go-to-k/cdkd#3662). The first round's arms 1 and 2 keep their
26145
+ * behaviour: a property they match really changed.
26146
+ *
26147
+ * @param round 1-based fixpoint round; see the speculative-promotion note.
26148
+ * @returns whether any synthetic PropertyChange was added (the caller's
26149
+ * fixpoint signal).
26150
+ */
26151
+ promoteInPlaceAttributeDependents(changes, desiredTemplate, rawGetAttRefs, currentResources, round) {
25691
26152
  const changedPropsByUpstream = /* @__PURE__ */ new Map();
25692
26153
  const inPlaceUpdateTypeByUpstream = /* @__PURE__ */ new Map();
25693
26154
  for (const [logicalId, change] of changes) {
@@ -25697,7 +26158,8 @@ var DiffCalculator = class DiffCalculator {
25697
26158
  const props = propertyChanges.map((pc) => pc.path).filter((p) => typeof p === "string");
25698
26159
  if (props.length > 0) changedPropsByUpstream.set(logicalId, new Set(props));
25699
26160
  }
25700
- if (changedPropsByUpstream.size === 0 && inPlaceUpdateTypeByUpstream.size === 0) return;
26161
+ if (changedPropsByUpstream.size === 0 && inPlaceUpdateTypeByUpstream.size === 0) return false;
26162
+ let added = false;
25701
26163
  for (const [dependentId, perProp] of rawGetAttRefs) {
25702
26164
  const resource = desiredTemplate.Resources[dependentId];
25703
26165
  if (!resource || resource.Type === "AWS::CDK::Metadata") continue;
@@ -25706,22 +26168,29 @@ var DiffCalculator = class DiffCalculator {
25706
26168
  if (change.changeType !== "NO_CHANGE" && change.changeType !== "UPDATE") continue;
25707
26169
  const existingPaths = new Set((change.propertyChanges ?? []).map((pc) => pc.path));
25708
26170
  const syntheticChanges = [];
26171
+ const speculationAllowed = !DiffCalculator.readsMaskedAttribute(perProp, currentResources);
25709
26172
  for (const [propKey, getAttRefs] of perProp) {
25710
26173
  if (existingPaths.has(propKey)) continue;
25711
26174
  let matched = false;
25712
26175
  for (const [upstreamId, attrs] of getAttRefs) {
25713
26176
  if (upstreamId === dependentId) continue;
26177
+ if (round > 1 && !speculationAllowed) break;
25714
26178
  const changedProps = changedPropsByUpstream.get(upstreamId);
25715
26179
  if (changedProps && [...attrs].some((attr) => changedProps.has(attr))) {
25716
26180
  matched = true;
25717
26181
  break;
25718
26182
  }
25719
26183
  const upstreamType = inPlaceUpdateTypeByUpstream.get(upstreamId);
25720
- const derivedAttrs = upstreamType ? IN_PLACE_UPDATE_DERIVED_ATTRS[upstreamType] : void 0;
26184
+ const derivedAttrs = upstreamType !== void 0 && Object.hasOwn(IN_PLACE_UPDATE_DERIVED_ATTRS, upstreamType) ? IN_PLACE_UPDATE_DERIVED_ATTRS[upstreamType] : void 0;
25721
26185
  if (derivedAttrs && [...attrs].some((attr) => derivedAttrs.has(attr))) {
25722
26186
  matched = true;
25723
26187
  break;
25724
26188
  }
26189
+ const derivedPrefixes = upstreamType !== void 0 && Object.hasOwn(IN_PLACE_UPDATE_DERIVED_ATTR_PREFIXES, upstreamType) ? IN_PLACE_UPDATE_DERIVED_ATTR_PREFIXES[upstreamType] : void 0;
26190
+ if (speculationAllowed && derivedPrefixes && [...attrs].some((attr) => derivedPrefixes.some((prefix) => attr.startsWith(prefix)))) {
26191
+ matched = true;
26192
+ break;
26193
+ }
25725
26194
  }
25726
26195
  if (!matched) continue;
25727
26196
  syntheticChanges.push({
@@ -25732,12 +26201,51 @@ var DiffCalculator = class DiffCalculator {
25732
26201
  });
25733
26202
  }
25734
26203
  if (syntheticChanges.length === 0) continue;
26204
+ added = true;
25735
26205
  if (change.changeType === "NO_CHANGE") {
25736
26206
  change.changeType = "UPDATE";
25737
26207
  change.propertyChanges = syntheticChanges;
25738
26208
  this.logger.debug(`UPDATE (in-place attr propagated): ${dependentId} reads a changed attribute of an updated resource`);
25739
26209
  } else change.propertyChanges = [...change.propertyChanges ?? [], ...syntheticChanges];
25740
26210
  }
26211
+ return added;
26212
+ }
26213
+ /**
26214
+ * Whether any `Fn::GetAtt` / `Fn::Sub` read of a dependent (its
26215
+ * {@link extractGetAttRefs} map, every property and every upstream) lands on
26216
+ * a PERSISTED attribute carrying the redaction mask. The attribute is found
26217
+ * the way the resolver finds it: the flat key, else the dot-separated path
26218
+ * walked through nested objects (a Cloud Control record's
26219
+ * `Endpoint.Address`).
26220
+ *
26221
+ * What it cannot see, and the engine still refuses on a speculative
26222
+ * promotion: a masked cross-stack read (`Fn::ImportValue` /
26223
+ * `Fn::GetStackOutput`), whose producer record is another stack's, and a
26224
+ * `Ref` whose state-key lookup lands on a masked property, since `Ref`
26225
+ * reads are not extracted.
26226
+ */
26227
+ static readsMaskedAttribute(perProp, currentResources) {
26228
+ for (const getAttRefs of perProp.values()) for (const [upstreamId, attrs] of getAttRefs) {
26229
+ const attributes = Object.hasOwn(currentResources, upstreamId) ? currentResources[upstreamId]?.attributes : void 0;
26230
+ if (!attributes) continue;
26231
+ for (const attr of attrs) if (carriesSecretMask(DiffCalculator.persistedAttribute(attributes, attr))) return true;
26232
+ }
26233
+ return false;
26234
+ }
26235
+ /**
26236
+ * A persisted attribute by the resolver's lookup rule: the own flat key, else
26237
+ * the dot-separated path through own keys of nested objects; `undefined`
26238
+ * when neither exists.
26239
+ */
26240
+ static persistedAttribute(attributes, attr) {
26241
+ if (Object.hasOwn(attributes, attr)) return attributes[attr];
26242
+ if (!attr.includes(".")) return void 0;
26243
+ let cursor = attributes;
26244
+ for (const part of attr.split(".")) {
26245
+ if (cursor === null || typeof cursor !== "object" || !Object.hasOwn(cursor, part)) return;
26246
+ cursor = cursor[part];
26247
+ }
26248
+ return cursor;
25741
26249
  }
25742
26250
  /**
25743
26251
  * Extract `Fn::GetAtt` / `Fn::Sub`-`${X.Attr}` references from a property value
@@ -25767,6 +26275,10 @@ var DiffCalculator = class DiffCalculator {
25767
26275
  if ("Fn::GetAtt" in obj) {
25768
26276
  const ga = obj["Fn::GetAtt"];
25769
26277
  if (Array.isArray(ga) && typeof ga[0] === "string" && typeof ga[1] === "string") add(ga[0], ga[1]);
26278
+ else if (typeof ga === "string") {
26279
+ const split = splitGetAttStringForm(ga);
26280
+ if (split) add(split.logicalId, split.attributeName);
26281
+ }
25770
26282
  return;
25771
26283
  }
25772
26284
  if ("Fn::Sub" in obj) {
@@ -26148,7 +26660,8 @@ function applyCallerIdentityCredentials(env) {
26148
26660
  //#endregion
26149
26661
  //#region src/utils/role-arn.ts
26150
26662
  /**
26151
- * Process-lifetime cache of assumed credentials keyed by RoleArn.
26663
+ * Process-lifetime cache of assumed credentials keyed by RoleArn AND the
26664
+ * SOURCE identity the `sts:AssumeRole` hop runs as.
26152
26665
  *
26153
26666
  * Storing the in-flight `Promise` (rather than the resolved value) collapses
26154
26667
  * concurrent first-time callers into a single `sts:AssumeRole` request. After
@@ -26156,10 +26669,19 @@ function applyCallerIdentityCredentials(env) {
26156
26669
  * stack that references the same producer N times via `Fn::GetStackOutput`
26157
26670
  * only pays the STS hop once.
26158
26671
  *
26159
- * The cache is keyed by RoleArn alone (not RoleArn + region) because STS
26160
- * credentials are global — assumed credentials work against any region's
26161
- * service endpoint. The downstream S3 client built from these credentials
26162
- * picks its own region via `GetBucketLocation`.
26672
+ * Not keyed by region, because STS credentials are global — assumed
26673
+ * credentials work against any region's service endpoint. The downstream S3
26674
+ * client built from these credentials picks its own region via
26675
+ * `GetBucketLocation`.
26676
+ *
26677
+ * KEYED BY THE SOURCE IDENTITY TOO (issue
26678
+ * [#3588](https://github.com/go-to-k/cdkd/issues/3588)): the hop runs as the
26679
+ * ACTIVE `AwsClients`' credential configuration, and a library caller can
26680
+ * install (`setAwsClients`) two different explicit credentials in one process,
26681
+ * as can cdkd's own per-stack scopes. Keyed by RoleArn alone, the second would be handed
26682
+ * credentials the FIRST identity obtained — a role the second one may not even
26683
+ * be trusted to assume. The source half is `credentialFingerprint`'s (profile +
26684
+ * access key id, never secret material).
26163
26685
  *
26164
26686
  * **Expiration handling**: on every cache hit, the cached credentials'
26165
26687
  * `expiration` is compared against `Date.now()` with a 60-second safety
@@ -26219,7 +26741,7 @@ function parseIamRoleArn(roleArn) {
26219
26741
  * the consumer account's normal credentials). Threading the credentials
26220
26742
  * through a fresh `S3Client` via this helper keeps the scope narrow.
26221
26743
  *
26222
- * **Why cache per-RoleArn for the process lifetime.** A multi-resource
26744
+ * **Why cache per RoleArn (and source identity) for the process lifetime.** A multi-resource
26223
26745
  * stack typically references `Fn::GetStackOutput` from many template sites
26224
26746
  * (every IAM policy / Lambda env / ALB listener that pulls a shared VPC ID
26225
26747
  * from a platform stack). Assuming the role once per deploy is sufficient;
@@ -26242,17 +26764,19 @@ function parseIamRoleArn(roleArn) {
26242
26764
  * uniformly), but the next caller after rejection gets a clean slate.
26243
26765
  */
26244
26766
  async function assumeRoleForCrossAccountStateRead(roleArn) {
26245
- const cached = crossAccountCredentialsCache.get(roleArn);
26767
+ const sourceConfig = ambientCredentialConfig();
26768
+ const cacheKey = JSON.stringify([credentialFingerprint(sourceConfig), roleArn]);
26769
+ const cached = crossAccountCredentialsCache.get(cacheKey);
26246
26770
  if (cached) {
26247
26771
  const cachedCreds = await cached;
26248
26772
  if (!cachedCreds.expiration || Date.now() < cachedCreds.expiration.getTime() - CRED_EXPIRY_SAFETY_MS) return cachedCreds;
26249
- crossAccountCredentialsCache.delete(roleArn);
26773
+ crossAccountCredentialsCache.delete(cacheKey);
26250
26774
  }
26251
26775
  const promise = (async () => {
26252
26776
  const logger = getLogger().child("role-arn");
26253
26777
  const displayRoleArn = displayIdent(roleArn, { maxCodePoints: 613 });
26254
26778
  logger.debug(`Assuming role for cross-account state read: ${displayRoleArn}`);
26255
- const sts = new STSClient({ ...awsClientDefaults() });
26779
+ const sts = new STSClient({ ...clientDefaultsFor(sourceConfig) });
26256
26780
  try {
26257
26781
  let response;
26258
26782
  try {
@@ -26279,10 +26803,10 @@ async function assumeRoleForCrossAccountStateRead(roleArn) {
26279
26803
  sts.destroy();
26280
26804
  }
26281
26805
  })().catch((err) => {
26282
- if (crossAccountCredentialsCache.get(roleArn) === promise) crossAccountCredentialsCache.delete(roleArn);
26806
+ if (crossAccountCredentialsCache.get(cacheKey) === promise) crossAccountCredentialsCache.delete(cacheKey);
26283
26807
  throw err;
26284
26808
  });
26285
- crossAccountCredentialsCache.set(roleArn, promise);
26809
+ crossAccountCredentialsCache.set(cacheKey, promise);
26286
26810
  return promise;
26287
26811
  }
26288
26812
  /**
@@ -28179,7 +28703,7 @@ var WAFv2WebACLProvider = class {
28179
28703
  ])]]);
28180
28704
  getClient() {
28181
28705
  if (!this.wafv2Client) this.wafv2Client = new WAFV2Client({
28182
- ...awsClientDefaults(),
28706
+ ...ambientClientDefaults(),
28183
28707
  ...this.providerRegion ? { region: this.providerRegion } : {}
28184
28708
  });
28185
28709
  return this.wafv2Client;
@@ -29603,6 +30127,18 @@ async function allSettledKeepingFirstRejection(promises, onAbandoned) {
29603
30127
  }
29604
30128
  }
29605
30129
  /**
30130
+ * The key for the resolver's per-region SDK client caches ({@link
30131
+ * IntrinsicFunctionResolver}'s `cfnClients`, `regionScopedClients` and
30132
+ * `serviceDiscoveryClients`): the region PLUS the credential fingerprint of the
30133
+ * configuration the cached client was built from (issue
30134
+ * [#3588](https://github.com/go-to-k/cdkd/issues/3588)). Encoded through
30135
+ * {@link injectiveKey}, so no region or profile spelling can forge another
30136
+ * pair's key.
30137
+ */
30138
+ function clientCacheKey(region, credentialConfig) {
30139
+ return injectiveKey(region, credentialFingerprint(credentialConfig));
30140
+ }
30141
+ /**
29606
30142
  * Is `region` safe to build an AWS SDK client from?
29607
30143
  *
29608
30144
  * This is a SECURITY gate, not an AWS region registry, and the distinction
@@ -30084,15 +30620,22 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30084
30620
  * foreign region per resolver, versus the ambient clients which are already
30085
30621
  * created and destroyed per stack by `deploy.ts`.
30086
30622
  *
30087
- * KEYED BY REGION ALONE, which means an entry also pins the CREDENTIAL
30088
- * configuration of whichever ambient instance was current at the first
30089
- * mismatch for that region. That is sound today because the credential half
30090
- * is process-wide rather than per-stack: `--profile` comes from one CLI
30091
- * option and `--role-arn` lands in `process.env`, so every ambient instance
30092
- * a run installs carries the same one. Widen the key to include
30093
- * {@link AwsClients.credentialConfig} the moment that stops being true —
30094
- * per-stack credentials would otherwise let one stack's lookups run under
30095
- * another's identity, which is a worse bug than the one this cache serves.
30623
+ * KEYED BY REGION AND CREDENTIAL FINGERPRINT ({@link clientCacheKey}, issue
30624
+ * [#3588](https://github.com/go-to-k/cdkd/issues/3588)). An entry pins the
30625
+ * credential configuration of the ambient instance it was derived from, and
30626
+ * that half is process-wide only for the CLI (one `--profile`, one
30627
+ * `--role-arn`): a LIBRARY caller can drive one resolver across two
30628
+ * `setAwsClients` installs (or cdkd's per-stack scopes) with different
30629
+ * explicit credentials, and keyed by region alone the second's lookups ran under the first's
30630
+ * identity. {@link cfnClients} and {@link serviceDiscoveryClients} share the
30631
+ * key for the same reason, and the two CloudFormation fallback memos
30632
+ * ({@link cfnExportsPromises}, {@link cfnStackOutputsCache}) carry the
30633
+ * fingerprint too.
30634
+ *
30635
+ * THE CLIENT HALF IS CLOSED, NOT THE WHOLE CLASS: {@link cachedDynamicReferences}
30636
+ * (keyed by the expression alone) and the PROCESS-GLOBAL account identity
30637
+ * behind `getAccountInfo` still assume one identity per resolver and per
30638
+ * process respectively — a value one identity read is served to the next.
30096
30639
  */
30097
30640
  regionScopedClients = /* @__PURE__ */ new Map();
30098
30641
  /**
@@ -30159,9 +30702,10 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30159
30702
  /**
30160
30703
  * Per-region CloudFormation clients for the cross-stack fallback
30161
30704
  * lookups (issue #1697). Keyed by region because `Fn::GetStackOutput`
30162
- * may target a region different from the consumer's deploy region.
30705
+ * may target a region different from the consumer's deploy region, and by
30706
+ * the credential fingerprint for {@link regionScopedClients}' reason.
30163
30707
  */
30164
- cfnClients = {};
30708
+ cfnClients = /* @__PURE__ */ new Map();
30165
30709
  /**
30166
30710
  * Memoized full `ListExports` listing for the `Fn::ImportValue`
30167
30711
  * fallback (issue #1697 review). Without it, EVERY cdkd-miss import
@@ -30173,14 +30717,18 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30173
30717
  * deploys. FAILED fetches are not cached (the rejection handler
30174
30718
  * clears the slot) so a transient throttle does not poison the rest
30175
30719
  * of the deploy's lookups.
30720
+ *
30721
+ * Keyed by the credential fingerprint (issue #3588): a listing is an answer
30722
+ * one identity was allowed to read, so it is never handed to another.
30176
30723
  */
30177
- cfnExportsPromise;
30724
+ cfnExportsPromises = /* @__PURE__ */ new Map();
30178
30725
  /**
30179
30726
  * Memoized per-(region, stack) `DescribeStacks` outputs for the
30180
30727
  * `Fn::GetStackOutput` fallback (issue #1697 review) — a stack
30181
30728
  * referencing the same CFn producer N times pays one call. Successful
30182
30729
  * lookups (including the definitive "stack does not exist" miss) are
30183
- * cached; lookup FAILURES are evicted so they are retried.
30730
+ * cached; lookup FAILURES are evicted so they are retried. Keyed by the
30731
+ * credential fingerprint as well, for {@link cfnExportsPromises}' reason.
30184
30732
  */
30185
30733
  cfnStackOutputsCache = /* @__PURE__ */ new Map();
30186
30734
  /**
@@ -30412,11 +30960,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30412
30960
  const loggedTarget = targetLogText ?? (targetRegion === this.explicitRegion ? this.explicitRegionLogText : void 0) ?? target;
30413
30961
  if (!isClientSafeRegion(target)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Refusing to build AWS clients for the region '${displaySafe(stripControlChars(loggedTarget)).slice(0, 64)}': it is not a valid AWS region name, and a region is substituted into the AWS service hostname.`));
30414
30962
  if (typeof ambient.withRegion !== "function") return ambient;
30415
- const cached = this.regionScopedClients.get(target);
30963
+ const cacheKey = clientCacheKey(target, ambient.credentialConfig ?? {});
30964
+ const cached = this.regionScopedClients.get(cacheKey);
30416
30965
  if (cached) return cached;
30417
30966
  if (canonicalizeRegion(ambient.configuredRegion) === target) return ambient;
30418
30967
  const scoped = ambient.withRegion(target);
30419
- this.regionScopedClients.set(target, scoped);
30968
+ this.regionScopedClients.set(cacheKey, scoped);
30420
30969
  this.logger.debug(`Using region-scoped AWS clients for ${displaySafe(loggedTarget)}`);
30421
30970
  return scoped;
30422
30971
  }
@@ -30447,21 +30996,21 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
30447
30996
  * `getAccountInfo`'s in-flight promise and reach here one at a time, which
30448
30997
  * the case says out loud. A REJECTED import is evicted
30449
30998
  * so a transient failure does not poison the rest of the deploy, mirroring
30450
- * `cfnExportsPromise`. Lifetime is the resolver's own, like
30999
+ * `cfnExportsPromises`. Lifetime is the resolver's own, like
30451
31000
  * {@link regionScopedClients} and {@link cfnClients}: at most one per region
30452
31001
  * per resolver, versus the one-per-CALL this replaces.
30453
31002
  */
30454
31003
  async serviceDiscoveryClient() {
30455
31004
  const scoped = this.clientsForRegion(this.explicitRegion);
30456
31005
  const region = scoped.configuredRegion;
30457
- const key = region ?? "";
31006
+ const credentialConfig = scoped.credentialConfig ?? {};
31007
+ const key = clientCacheKey(region ?? "", credentialConfig);
30458
31008
  const cached = this.serviceDiscoveryClients.get(key);
30459
31009
  if (cached) return cached;
30460
31010
  const building = (async () => {
30461
31011
  const { ServiceDiscoveryClient } = await import("@aws-sdk/client-servicediscovery");
30462
31012
  return new ServiceDiscoveryClient({
30463
- ...awsClientDefaults({ profile: scoped.credentialConfig?.profile }),
30464
- ...scoped.credentialConfig ?? {},
31013
+ ...clientDefaultsFor(credentialConfig),
30465
31014
  ...region ? { region } : {}
30466
31015
  });
30467
31016
  })();
@@ -31106,12 +31655,15 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
31106
31655
  }
31107
31656
  if (resource.resourceType === "AWS::Route53::HostedZone" && attributeName === "NameServers" && typeof flatValue === "string") {
31108
31657
  const nameServers = flatValue === "" ? [] : flatValue.split(",");
31658
+ const notedNameServers = this.noteAttributeSecrecy(logicalId, attributeName, nameServers, context);
31109
31659
  this.logger.debug(`Normalized legacy Fn::GetAtt attribute: ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(nameServers, context)), context)}`);
31110
- return this.noteAttributeSecrecy(logicalId, attributeName, nameServers, context);
31660
+ return notedNameServers;
31111
31661
  }
31662
+ const reresolvesNestedReference = resource.resourceType === NESTED_STACK_RESOURCE_TYPE$2 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) && carriesDynamicReference(flatValue);
31663
+ const notedFlatValue = reresolvesNestedReference ? flatValue : this.noteAttributeSecrecy(logicalId, attributeName, flatValue, context);
31112
31664
  this.logger.debug(`Resolved Fn::GetAtt from attributes: ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(flatValue, context)), context)}`);
31113
- if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE$2 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) && carriesDynamicReference(flatValue)) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${this.displayLeaf(attributeName, context)}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
31114
- return this.noteAttributeSecrecy(logicalId, attributeName, flatValue, context);
31665
+ if (reresolvesNestedReference) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${this.displayLeaf(attributeName, context)}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
31666
+ return notedFlatValue;
31115
31667
  }
31116
31668
  if (attributeName.includes(".")) {
31117
31669
  const parts = attributeName.split(".");
@@ -31122,8 +31674,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
31122
31674
  break;
31123
31675
  }
31124
31676
  if (cursor !== void 0) {
31677
+ const notedCursor = this.noteAttributeSecrecy(logicalId, attributeName, cursor, context);
31125
31678
  this.logger.debug(`Resolved Fn::GetAtt from nested attributes: ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(cursor, context)), context)}`);
31126
- return this.noteAttributeSecrecy(logicalId, attributeName, cursor, context);
31679
+ return notedCursor;
31127
31680
  }
31128
31681
  }
31129
31682
  }
@@ -31182,6 +31735,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
31182
31735
  *
31183
31736
  * A context supplying NEITHER field — the diff / no-op resolver, `cdkd
31184
31737
  * scrub`, `cdkd import` — pays two undefined checks and gets its value back.
31738
+ *
31739
+ * ORDERING: call it BEFORE any log line that prints the value
31740
+ * (go-to-k/cdkd#3659). Note 1 is what puts a `NoEcho` value into the bag
31741
+ * `displayMasked` masks against, so a line logged first prints the plaintext
31742
+ * at `--verbose`.
31185
31743
  */
31186
31744
  noteAttributeSecrecy(logicalId, attributeName, value, context) {
31187
31745
  const declared = context.noEchoAttributeResources?.get(logicalId);
@@ -31316,8 +31874,9 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
31316
31874
  }
31317
31875
  /** Serve a value the #1852 heal just read from AWS, logging it like a cached read. */
31318
31876
  serveHealedAttribute(logicalId, attributeName, value, context) {
31877
+ const noted = this.noteAttributeSecrecy(logicalId, attributeName, value, context);
31319
31878
  this.logger.debug(`Resolved Fn::GetAtt from a re-read of AWS (the state record lacked it): ${this.displayMasked(logicalId, context)}.${this.displayMasked(attributeName, context)} -> ${this.displayMasked(stringifyAttributeForLog(attributeName, this.maskValueLeaves(value, context)), context)}`);
31320
- return this.noteAttributeSecrecy(logicalId, attributeName, value, context);
31879
+ return noted;
31321
31880
  }
31322
31881
  /**
31323
31882
  * {@link constructGuardedAttribute}, healing a STALE state record first when
@@ -32857,12 +33416,14 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
32857
33416
  * the same not-found error anyway.
32858
33417
  */
32859
33418
  async lookupCfnExport(exportName, context) {
32860
- let listing = this.cfnExportsPromise;
33419
+ const listingKey = credentialFingerprint(ambientCredentialConfig());
33420
+ let listing = this.cfnExportsPromises.get(listingKey);
32861
33421
  if (!listing) {
32862
- listing = this.fetchAllCfnExports();
32863
- this.cfnExportsPromise = listing;
32864
- listing.catch(() => {
32865
- if (this.cfnExportsPromise === listing) this.cfnExportsPromise = void 0;
33422
+ const fetched = this.fetchAllCfnExports();
33423
+ listing = fetched;
33424
+ this.cfnExportsPromises.set(listingKey, fetched);
33425
+ fetched.catch(() => {
33426
+ if (this.cfnExportsPromises.get(listingKey) === fetched) this.cfnExportsPromises.delete(listingKey);
32866
33427
  });
32867
33428
  }
32868
33429
  try {
@@ -32932,7 +33493,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
32932
33493
  * contract as {@link lookupCfnExport}.
32933
33494
  */
32934
33495
  async lookupCfnStackOutputs(stackName, region, context, loggedRegionText) {
32935
- const cacheKey = injectiveKey(region, stackName);
33496
+ const cacheKey = injectiveKey(region, stackName, credentialFingerprint(ambientCredentialConfig()));
32936
33497
  let fetch = this.cfnStackOutputsCache.get(cacheKey);
32937
33498
  if (!fetch) {
32938
33499
  fetch = this.fetchCfnStackOutputs(stackName, region);
@@ -32983,19 +33544,19 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
32983
33544
  * Not routed through {@link clientsForRegion}: the region here is always
32984
33545
  * the one the caller named, never the ambient's, and a test double without
32985
33546
  * `credentialConfig` degrades to the default chain rather than throwing.
32986
- * Keyed by region alone for the reason {@link regionScopedClients} gives:
32987
- * the credential half is process-wide.
33547
+ * Keyed by region AND credential fingerprint for the reason
33548
+ * {@link regionScopedClients} gives (issue #3588).
32988
33549
  */
32989
33550
  getCfnClient(region) {
32990
- let client = this.cfnClients[region];
33551
+ const credentialConfig = ambientCredentialConfig();
33552
+ const key = clientCacheKey(region, credentialConfig);
33553
+ let client = this.cfnClients.get(key);
32991
33554
  if (!client) {
32992
- const credentialConfig = getAwsClients().credentialConfig ?? {};
32993
33555
  client = new CloudFormationClient({
32994
- ...awsClientDefaults({ profile: credentialConfig.profile }),
32995
- ...credentialConfig,
33556
+ ...clientDefaultsFor(credentialConfig),
32996
33557
  region
32997
33558
  });
32998
- this.cfnClients[region] = client;
33559
+ this.cfnClients.set(key, client);
32999
33560
  }
33000
33561
  return client;
33001
33562
  }
@@ -35272,6 +35833,15 @@ const CC_PROTECTION_PROPERTIES = {
35272
35833
  function ccProtectionProperty(resourceType) {
35273
35834
  return CC_PROTECTION_PROPERTIES[resourceType];
35274
35835
  }
35836
+ /**
35837
+ * Every resource type registered in the CC protection registry. Consumed by
35838
+ * the cross-site consistency test that keeps this registry, the destroy
35839
+ * confirm-prompt count map, the `--remove-protection` help strings, and the
35840
+ * docs tables from drifting apart.
35841
+ */
35842
+ function ccProtectionRegistryTypes() {
35843
+ return Object.keys(CC_PROTECTION_PROPERTIES);
35844
+ }
35275
35845
 
35276
35846
  //#endregion
35277
35847
  //#region src/provisioning/unsupported-types.generated.ts
@@ -36328,7 +36898,7 @@ var CloudControlProvider = class {
36328
36898
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
36329
36899
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
36330
36900
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
36331
- const { ASGProvider } = await import("./asg-provider-CHM7_WdJ.js").then((n) => n.n);
36901
+ const { ASGProvider } = await import("./asg-provider-Dpki2-Xd.js").then((n) => n.n);
36332
36902
  const asgProvider = new ASGProvider();
36333
36903
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
36334
36904
  }
@@ -36878,7 +37448,7 @@ var CloudControlProvider = class {
36878
37448
  break;
36879
37449
  case "AWS::RDS::DBCluster":
36880
37450
  try {
36881
- const cluster = (await new RDSClient({ ...awsClientDefaults() }).send(new DescribeDBClustersCommand({ DBClusterIdentifier: physicalId }))).DBClusters?.[0];
37451
+ const cluster = (await new RDSClient({ ...ambientClientDefaults() }).send(new DescribeDBClustersCommand({ DBClusterIdentifier: physicalId }))).DBClusters?.[0];
36882
37452
  if (cluster) {
36883
37453
  if (cluster.Endpoint) enriched["Endpoint.Address"] = cluster.Endpoint;
36884
37454
  if (cluster.Port !== void 0) enriched["Endpoint.Port"] = String(cluster.Port);
@@ -36893,7 +37463,7 @@ var CloudControlProvider = class {
36893
37463
  break;
36894
37464
  case "AWS::RDS::DBInstance":
36895
37465
  try {
36896
- const inst = (await new RDSClient({ ...awsClientDefaults() }).send(new DescribeDBInstancesCommand({ DBInstanceIdentifier: physicalId }))).DBInstances?.[0];
37466
+ const inst = (await new RDSClient({ ...ambientClientDefaults() }).send(new DescribeDBInstancesCommand({ DBInstanceIdentifier: physicalId }))).DBInstances?.[0];
36897
37467
  if (inst) {
36898
37468
  if (inst.Endpoint?.Address) enriched["Endpoint.Address"] = inst.Endpoint.Address;
36899
37469
  if (inst.Endpoint?.Port !== void 0) enriched["Endpoint.Port"] = String(inst.Endpoint.Port);
@@ -37047,7 +37617,7 @@ var CloudControlProvider = class {
37047
37617
  break;
37048
37618
  case "AWS::ElastiCache::ReplicationGroup":
37049
37619
  try {
37050
- const rg = (await new ElastiCacheClient({ ...awsClientDefaults() }).send(new DescribeReplicationGroupsCommand({ ReplicationGroupId: physicalId }))).ReplicationGroups?.[0];
37620
+ const rg = (await new ElastiCacheClient({ ...ambientClientDefaults() }).send(new DescribeReplicationGroupsCommand({ ReplicationGroupId: physicalId }))).ReplicationGroups?.[0];
37051
37621
  if (rg) {
37052
37622
  const primaryNode = rg.NodeGroups?.[0];
37053
37623
  if (primaryNode?.PrimaryEndpoint?.Address) enriched["PrimaryEndPoint.Address"] = primaryNode.PrimaryEndpoint.Address;
@@ -37069,7 +37639,7 @@ var CloudControlProvider = class {
37069
37639
  break;
37070
37640
  case "AWS::Redshift::Cluster":
37071
37641
  try {
37072
- const cluster = (await new RedshiftClient({ ...awsClientDefaults() }).send(new DescribeClustersCommand({ ClusterIdentifier: physicalId }))).Clusters?.[0];
37642
+ const cluster = (await new RedshiftClient({ ...ambientClientDefaults() }).send(new DescribeClustersCommand({ ClusterIdentifier: physicalId }))).Clusters?.[0];
37073
37643
  if (cluster?.Endpoint) {
37074
37644
  if (cluster.Endpoint.Address) enriched["Endpoint.Address"] = cluster.Endpoint.Address;
37075
37645
  if (cluster.Endpoint.Port !== void 0) enriched["Endpoint.Port"] = String(cluster.Endpoint.Port);
@@ -37081,7 +37651,7 @@ var CloudControlProvider = class {
37081
37651
  break;
37082
37652
  case "AWS::OpenSearchService::Domain":
37083
37653
  try {
37084
- const domain = (await new OpenSearchClient({ ...awsClientDefaults() }).send(new DescribeDomainCommand({ DomainName: physicalId }))).DomainStatus;
37654
+ const domain = (await new OpenSearchClient({ ...ambientClientDefaults() }).send(new DescribeDomainCommand({ DomainName: physicalId }))).DomainStatus;
37085
37655
  if (domain) {
37086
37656
  const endpoint = domain.Endpoint ?? domain.Endpoints?.["vpc"];
37087
37657
  if (endpoint) enriched["DomainEndpoint"] = endpoint;
@@ -41576,7 +42146,8 @@ var ProviderRegistry = class {
41576
42146
  provisionedBy: "sdk"
41577
42147
  };
41578
42148
  }
41579
- if (isNonProvisionable(resourceType) || specificProvider.disableCcApiFallback === true) throw new Error(this.buildUnroutableSilentDropMessage(resourceType, actionableDrops));
42149
+ const unroutable = this.ccRouteUnavailableReason(resourceType);
42150
+ if (unroutable !== void 0) throw new Error(this.buildUnroutableSilentDropMessage(resourceType, actionableDrops, unroutable));
41580
42151
  this.logger.debug(`Auto-routing ${resourceType} via Cloud Control (silent-drop properties: ${actionableDrops.map((d) => d.property).join(", ")})`);
41581
42152
  return {
41582
42153
  provider: this.cloudControlProvider,
@@ -41601,19 +42172,29 @@ var ProviderRegistry = class {
41601
42172
  throw new Error(`No provider available for resource type: ${resourceType}. This resource type is not supported by Cloud Control API and no SDK provider is registered.`);
41602
42173
  }
41603
42174
  /**
42175
+ * Why the Cloud Control auto-route cannot serve `resourceType`, or
42176
+ * `undefined` when it can. The ONE predicate both refusal sites
42177
+ * (`getProviderFor` and `reportSilentDropDecisions`) and the accepted-drop
42178
+ * warn read, so the warn that predicts what a flag-less deploy does cannot
42179
+ * disagree with the refusal that deploy then hits (issue #2792).
42180
+ */
42181
+ ccRouteUnavailableReason(resourceType) {
42182
+ if (isNonProvisionable(resourceType)) return "ProvisioningType: NON_PROVISIONABLE — Cloud Control has no handlers for it";
42183
+ if (this.providers.get(resourceType)?.disableCcApiFallback === true) return "the type's SDK provider opts out of the Cloud Control fallback (disableCcApiFallback)";
42184
+ }
42185
+ /**
41604
42186
  * Error message for a resource whose template uses SDK-provider-unhandled
41605
42187
  * properties on a type where the Cloud Control auto-route (issue #614) is
41606
42188
  * NOT viable — `ProvisioningType: NON_PROVISIONABLE` (no CC handlers) or a
41607
- * provider-level `disableCcApiFallback` opt-out. Includes each property's
42189
+ * provider-level `disableCcApiFallback` opt-out; `reason` is
42190
+ * {@link ccRouteUnavailableReason}'s answer. Includes each property's
41608
42191
  * `unhandledByDesign` rationale so the user sees WHY it is unhandled, plus
41609
42192
  * the `--allow-unsupported-properties` escape hatch (which forces the SDK
41610
42193
  * path and accepts the drop — the provider may still reject the resource
41611
42194
  * if the property is load-bearing, e.g. a non-Lustre FSx variant config).
41612
42195
  */
41613
- buildUnroutableSilentDropMessage(resourceType, drops) {
41614
- const details = drops.map((d) => ` - ${d.property}: ${d.rationale}`).join("\n");
41615
- const overrideHint = drops.map((d) => `${resourceType}:${d.property}`).join(",");
41616
- return `${resourceType} uses properties cdkd's SDK Provider does not handle, and this type cannot fall back to Cloud Control API (${isNonProvisionable(resourceType) ? "ProvisioningType: NON_PROVISIONABLE — Cloud Control has no handlers for it" : "the type's SDK provider opts out of the Cloud Control fallback (disableCcApiFallback)"}):\n${details}\nRemove the properties, or force the SDK provider path and accept the drop via --prefer-sdk-route ${overrideHint} (the provider may still reject the resource if the property is required).`;
42196
+ buildUnroutableSilentDropMessage(resourceType, drops, reason) {
42197
+ return `${resourceType} uses properties cdkd's SDK Provider does not handle, and this type cannot fall back to Cloud Control API (${reason}):\n${drops.map((d) => ` - ${d.property}: ${d.rationale}`).join("\n")}\nRemove the properties, or force the SDK provider path and accept the drop via --prefer-sdk-route ${drops.map((d) => `${resourceType}:${d.property}`).join(",")} (the provider may still reject the resource if the property is required).`;
41617
42198
  }
41618
42199
  /**
41619
42200
  * Legacy entry point that returns just the provider. Delegates to
@@ -41795,8 +42376,8 @@ var ProviderRegistry = class {
41795
42376
  const overridden = stickyCc ? [] : findAcceptedSilentDrops(resourceType, properties, this.allowedUnsupportedProperties);
41796
42377
  const autoRouted = drops.map(({ property }) => property).filter((property) => !this.allowedUnsupportedProperties.has(`${resourceType}:${property}`));
41797
42378
  if (autoRouted.length > 0) {
41798
- const provider = this.providers.get(resourceType);
41799
- if (isNonProvisionable(resourceType) || provider?.disableCcApiFallback === true) throw new Error(`${logicalId}: ${this.buildUnroutableSilentDropMessage(resourceType, drops.filter((d) => autoRouted.includes(d.property)))}`);
42379
+ const unroutable = this.ccRouteUnavailableReason(resourceType);
42380
+ if (unroutable !== void 0) throw new Error(`${logicalId}: ${this.buildUnroutableSilentDropMessage(resourceType, drops.filter((d) => autoRouted.includes(d.property)), unroutable)}`);
41800
42381
  const message = `${logicalId} (${resourceType}): routing via Cloud Control API (cdkd's SDK Provider does not yet wire ${autoRouted.join(", ")} — CC API will forward the full property map. Override via --prefer-sdk-route ${autoRouted.map((p) => `${resourceType}:${p}`).join(",")}.)`;
41801
42382
  if (provisionedBy === "cc-api") this.logger.debug(message);
41802
42383
  else this.logger.info(message);
@@ -41810,9 +42391,11 @@ var ProviderRegistry = class {
41810
42391
  }
41811
42392
  if (overridden.length > 0) {
41812
42393
  const createOnly = getPropertyCoverage(resourceType)?.createOnlyDrops;
41813
- const reroutable = overridden.filter((p) => createOnly?.has(p) !== true);
41814
- const needsRecreate = overridden.filter((p) => createOnly?.has(p) === true);
42394
+ const unroutable = this.ccRouteUnavailableReason(resourceType);
42395
+ const reroutable = unroutable === void 0 ? overridden.filter((p) => createOnly?.has(p) !== true) : [];
42396
+ const needsRecreate = unroutable === void 0 ? overridden.filter((p) => createOnly?.has(p) === true) : [];
41815
42397
  const remedies = [];
42398
+ if (unroutable !== void 0) remedies.push(`This type cannot be routed via Cloud Control API (${unroutable}), so removing the override makes the deploy refuse at pre-flight instead of rerouting; cdkd has no route that applies ${overridden.length === 1 ? "it" : "them"} to this type.`);
41816
42399
  if (reroutable.length > 0) remedies.push(`Remove the override for ${reroutable.join(", ")} to route this resource via Cloud Control API instead.`);
41817
42400
  if (needsRecreate.length > 0) {
41818
42401
  const one = needsRecreate.length === 1;
@@ -44305,8 +44888,11 @@ async function planOrphanAdoption(params) {
44305
44888
  const claims = async () => siblingClaims ??= await params.readSiblingClaims();
44306
44889
  for (const record of params.records) {
44307
44890
  const { logicalId, state } = record;
44891
+ const shownId = displayIdent(logicalId);
44892
+ const shownType = displayIdent(state.resourceType);
44893
+ const shownPhysicalId = displaySafe(state.physicalId);
44308
44894
  if (params.managedLogicalIds.has(logicalId)) {
44309
- params.logger.debug(`orphan ${logicalId}: already present in state.resources — dropping the stale record`);
44895
+ params.logger.debug(`orphan ${shownId}: already present in state.resources — dropping the stale record`);
44310
44896
  continue;
44311
44897
  }
44312
44898
  let provider;
@@ -44314,12 +44900,12 @@ async function planOrphanAdoption(params) {
44314
44900
  provider = params.getProvider(state.resourceType, state.provisionedBy);
44315
44901
  } catch (error) {
44316
44902
  outcome.remaining.push(record);
44317
- outcome.notices.push(`${logicalId} (${state.resourceType}) is still in AWS as ${state.physicalId} from an earlier rollback, but this build cannot route that type (${error instanceof Error ? error.message : String(error)}) — cdkd is not adopting it.`);
44903
+ outcome.notices.push(`${shownId} (${shownType}) is still in AWS as ${shownPhysicalId} from an earlier rollback, but this build cannot route that type (${displayAwsMessage(error instanceof Error ? error.message : String(error))}) — cdkd is not adopting it.`);
44318
44904
  continue;
44319
44905
  }
44320
44906
  if (!provider.import) {
44321
44907
  outcome.remaining.push(record);
44322
- outcome.notices.push(`${logicalId} (${state.resourceType}) was left in AWS by an earlier rollback as ${state.physicalId}, but its provider cannot verify it — cdkd is not adopting it.`);
44908
+ outcome.notices.push(`${shownId} (${shownType}) was left in AWS by an earlier rollback as ${shownPhysicalId}, but its provider cannot verify it — cdkd is not adopting it.`);
44323
44909
  continue;
44324
44910
  }
44325
44911
  let found;
@@ -44333,32 +44919,32 @@ async function planOrphanAdoption(params) {
44333
44919
  knownPhysicalId: state.physicalId
44334
44920
  });
44335
44921
  } catch (error) {
44336
- outcome.notices.push(`${logicalId} (${state.resourceType}) is recorded as left in AWS as ${state.physicalId}, but cdkd could not confirm it exists (${error instanceof Error ? error.message : String(error)}) — keeping the record and not adopting it this run.`);
44922
+ outcome.notices.push(`${shownId} (${shownType}) is recorded as left in AWS as ${shownPhysicalId}, but cdkd could not confirm it exists (${displayAwsMessage(error instanceof Error ? error.message : String(error))}) — keeping the record and not adopting it this run.`);
44337
44923
  outcome.remaining.push(record);
44338
44924
  continue;
44339
44925
  }
44340
44926
  if (found !== null && found.physicalId !== state.physicalId) {
44341
44927
  outcome.remaining.push(record);
44342
- outcome.notices.push(`${logicalId} (${state.resourceType}): cdkd asked about ${state.physicalId} and its provider answered for ${found.physicalId} — not adopting.`);
44928
+ outcome.notices.push(`${shownId} (${shownType}): cdkd asked about ${shownPhysicalId} and its provider answered for ${displaySafe(found.physicalId)} — not adopting.`);
44343
44929
  continue;
44344
44930
  }
44345
44931
  if (found === null) {
44346
- params.logger.debug(`orphan ${logicalId}: ${state.physicalId} no longer exists in AWS — dropping the record`);
44932
+ params.logger.debug(`orphan ${shownId}: ${shownPhysicalId} no longer exists in AWS — dropping the record`);
44347
44933
  continue;
44348
44934
  }
44349
44935
  if (params.nameProperties(state.resourceType).length === 0 || ADOPTION_REFUSED_TYPES.has(state.resourceType)) {
44350
44936
  outcome.remaining.push(record);
44351
44937
  const why = ADOPTION_REFUSED_TYPES.has(state.resourceType) ? "a new deploy of it mints a new resource rather than colliding, and cdkd cannot update one in place — so adopting it would end in a replacement that destroys what DeletionPolicy: Retain preserved" : "cdkd does not derive that resource's physical name, so a new deploy mints a new resource instead of colliding";
44352
- outcome.notices.push(`${logicalId} (${state.resourceType}) is still in AWS as ${state.physicalId} from an earlier rollback. cdkd does not re-adopt this type: ${why}. Delete it yourself when you no longer need it.`);
44938
+ outcome.notices.push(`${shownId} (${shownType}) is still in AWS as ${shownPhysicalId} from an earlier rollback. cdkd does not re-adopt this type: ${why}. Delete it yourself when you no longer need it.`);
44353
44939
  continue;
44354
44940
  }
44355
44941
  if (!templateStillDeclares(params.template, record, params.nameProperties(state.resourceType))) {
44356
44942
  outcome.remaining.push(record);
44357
- outcome.notices.push(`${logicalId} (${state.resourceType}) is still in AWS as ${state.physicalId} from an earlier rollback. This deploy does not create it under that name, so cdkd is leaving it alone.`);
44943
+ outcome.notices.push(`${shownId} (${shownType}) is still in AWS as ${shownPhysicalId} from an earlier rollback. This deploy does not create it under that name, so cdkd is leaving it alone.`);
44358
44944
  continue;
44359
44945
  }
44360
44946
  if ((await claims()).has(state.physicalId)) {
44361
- outcome.refusals.push(`${logicalId}: ${state.physicalId} is already recorded by another cdkd stack. cdkd will not adopt a resource another stack manages.`);
44947
+ outcome.refusals.push(`${shownId}: ${shownPhysicalId} is already recorded by another cdkd stack. cdkd will not adopt a resource another stack manages.`);
44362
44948
  outcome.remaining.push(record);
44363
44949
  continue;
44364
44950
  }
@@ -44411,7 +44997,7 @@ function makeSiblingClaimReader(params) {
44411
44997
  try {
44412
44998
  refs = await stateBackend.listStacks();
44413
44999
  } catch (error) {
44414
- logger.debug(`orphan adoption: could not list sibling stacks — ${error instanceof Error ? error.message : String(error)}`);
45000
+ logger.debug(`orphan adoption: could not list sibling stacks — ${displayAwsMessage(error instanceof Error ? error.message : String(error))}`);
44415
45001
  return claimed;
44416
45002
  }
44417
45003
  for (const ref of refs) {
@@ -44421,7 +45007,7 @@ function makeSiblingClaimReader(params) {
44421
45007
  const sibling = await stateBackend.getState(ref.stackName, ref.region);
44422
45008
  for (const record of Object.values(sibling?.state.resources ?? {})) claimed.add(record.physicalId);
44423
45009
  } catch (error) {
44424
- logger.debug(`orphan adoption: skipping unreadable state for ${ref.stackName} — ${error instanceof Error ? error.message : String(error)}`);
45010
+ logger.debug(`orphan adoption: skipping unreadable state for ${displayStackName(ref.stackName)} — ${displayAwsMessage(error instanceof Error ? error.message : String(error))}`);
44425
45011
  }
44426
45012
  }
44427
45013
  return claimed;
@@ -49762,7 +50348,9 @@ var DeployEngine = class DeployEngine {
49762
50348
  this.healBaseline = currentState.resources ?? {};
49763
50349
  refuseMalformedOutputs(currentState, stackName, this.stackRegion);
49764
50350
  refuseMalformedResourcesForDeploy(currentState, stackName, this.stackRegion);
50351
+ refuseMalformedResourceEntriesForDeploy(currentState, stackName, this.stackRegion);
49765
50352
  refuseMalformedOrphans(currentState, stackName, this.stackRegion);
50353
+ refuseMalformedOrphanRecords(currentState, stackName, this.stackRegion);
49766
50354
  const migrationPending = currentStateData?.migrationPending ?? false;
49767
50355
  this.logger.debug(`Loaded current state: ${Object.keys(currentState.resources).length} resources`);
49768
50356
  if (this.options.onCurrentStateLoaded) await this.options.onCurrentStateLoaded(stackName, currentStateData?.state);
@@ -50342,7 +50930,7 @@ var DeployEngine = class DeployEngine {
50342
50930
  if (plan.refusals.length > 0) throw new Error(`Deploy refused — cdkd left ${plan.refusals.length} resource(s) in AWS that it cannot safely re-adopt:\n ${plan.refusals.join("\n ")}`);
50343
50931
  for (const [logicalId, record] of Object.entries(plan.adopted)) {
50344
50932
  currentState.resources[logicalId] = record;
50345
- this.logger.info(`Adopting ${logicalId} (${record.resourceType}) left in AWS by an earlier rollback as ${record.physicalId}`);
50933
+ this.logger.info(`Adopting ${displayIdent(logicalId)} (${displayIdent(record.resourceType)}) left in AWS by an earlier rollback as ${displaySafe(record.physicalId)}`);
50346
50934
  }
50347
50935
  if (records.length > 0) currentState.orphans = plan.remaining;
50348
50936
  return plan;
@@ -51809,5 +52397,5 @@ var DeployEngine = class DeployEngine {
51809
52397
  };
51810
52398
 
51811
52399
  //#endregion
51812
- export { yellow as $, isCdkdError as $a, findLargeInlineResources as $i, repairMalformedOutputsForReadOnly as $n, WorkGraph as $r, replayWarn as $t, createPreDeleteFinalSnapshot as A, setAwsClients as Aa, runDockerStreaming as Ai, malformedOrphansRefusalMessage as An, scrubResourceRecord as Ar, isTerminationProtectionPropagationError as At, isStatefulRecreateTargetSync as B, LocalStartServiceError as Ba, resolveApp as Bi, refuseMalformedNestedChildOutputs as Bn, DEFAULT_STATE_PREFIX as Br, parameterTypeMayLoseSecretIdentity as Bt, namesAnyDeclaredParameter as C, resolveAssemblyPath as Ca, formatDockerLoginError as Ci, hasReadableOutputs as Cn, maskSecretsInError as Cr, CloudControlProvider as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, getAwsClients as Da, partitionSensitiveEnv as Di, malformedExportSourceWarning as Dn, recordMaskOnlyValuesIn as Dr, deleteSkipReason as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, AwsClients as Ea, isMalformedEnvKey as Ei, malformedExportNamesWarning as En, recordMaskOnlyValue as Er, deleteIndeterminateGuards as Et, extractDeploymentEventError as F, DependencyError as Fa, getDockerImageBySourceHash as Fi, malformedResourceEntriesWarning as Fn, buildForceUnlockCommand as Fr, coerceParameterTypedValue as Ft, toCfnInteger as G, ProvisioningError as Ga, resolveStateBucketWithDefaultAndSource as Gi, refuseMalformedOutputsForDestroy as Gn, importableOutputKeys as Gr, resolveExplicitPhysicalId as Gt, WARM_THROUGHPUT_MEMBERS as H, MalformedProducerRecordRefusalError as Ha, resolveCaptureObservedState as Hi, refuseMalformedOrphansForDestroy as Hn, hasReadableExportSet as Hr, isSettledInstanceState as Ht, makeSiblingClaimReader as I, DeployCancelledError as Ia, Synthesizer as Ii, malformedResourcePropertiesWarning as In, buildLockContentionMessage as Ir, getAccountInfo as It, bold as J, StackHasActiveImportsError as Ja, warnDeprecatedNoPrefixCliFlag as Ji, refuseMalformedResourceEntriesForOrphan as Jn, orphansAfterRollback as Jr, coerceCfnInteger as Jt, toFiniteNumber as K, ResourceTimeoutError as Ka, resolveUseCdkBootstrapAssets as Ki, refuseMalformedResourceAttributesForOrphan as Kn, importableOutputs as Kr, assertRegionMatch as Kt, planOrphanAdoption as L, DynamicReferenceRegionAmbiguousError as La, synthesisStatusMessage as Li, malformedResourcesWarning as Ln, forceQuitRecoveryClause as Lr, isImpossibleEmptyStoredAttribute as Lt, refusesFinalSnapshot as M, CdkdError as Ma, escapeRegExp$1 as Mi, malformedOutputsRefusalMessage as Mn, S3StateBackend as Mr, carriesDynamicReference as Mt, unsupportedFinalSnapshotError as N, ConfigError as Na, stripControlChars as Ni, malformedOutputsWarning as Nn, rebuildClientForBucketRegion as Nr, carriesFetchableDynamicReference as Nt, buildFinalSnapshotIdentifier as O, resetAwsClients as Oa, redactDockerArgvValues as Oi, malformedLocalOutputsWarning as On, recoverMaskedOutput as Or, isWaitAbandonedError as Ot, makeCanonicalizePropertiesFn as P, CrossAccountSecretRefusalError as Pa, AssetManifestLoader as Pi, malformedRenderedContainersWarning as Pn, UNREPRODUCIBLE_LOCK_CLAUSE as Pr, cfnRefValueFromPhysicalId as Pt, red as Q, formatError as Qa, MIGRATE_TMP_PREFIX as Qi, repairMalformedOrphansForReadOnly as Qn, stringifyValue as Qr, readConfigString as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, IntrinsicResolutionRefusalError as Ra, getDefaultStateBucketName as Ri, malformedStateRefusalMessage as Rn, formatLockExpiry as Rr, isNamelessDynamicReferenceError as Rt, computeParameterTaint as S, renderAssemblyPathEscape as Sa, finchSecretArgvRefusal as Si, hasReadableOrphans as Sn, markSameGenerationBag as Sr, startInterruptWatch as St, resourcesNamingDeclaredParameter as T, resolveBucketRegion as Ta, isDockerClientEnvKey as Ti, isReadableResourceEntry as Tn, pathCrossesDottedKey as Tr, UNSPECIFIED_SKIP_REASON as Tt, coerceWarmThroughput as U, NestedStackChildDirectDestroyError as Ua, resolveSkipPrefix as Ui, refuseMalformedOrphansForOrphan as Un, hasReasonlessBaselineRefusal as Ur, WAFv2WebACLProvider as Ut, renderStatefulReason as V, LockError as Va, resolveAutoAssetStorage as Vi, refuseMalformedOrphans as Vn, exportNamesCarriedFrom as Vr, refStateLookupFromResource as Vt, isWarmThroughputDecrease as W, PartialFailureError as Wa, resolveStateBucketWithDefault as Wi, refuseMalformedOutputs as Wn, hasUnverifiableParameterRefusal as Wr, normalizeAwsTagsToCfn as Wt, gray as X, StateError as Xa, CFN_TEMPLATE_BODY_LIMIT as Xi, refuseMalformedResourcesForDestroy as Xn, shouldRetainResource as Xr, configIntegerRefusal as Xt, cyan as Y, StackTerminationProtectionError as Ya, ssmResolvedValueType as Yi, refuseMalformedResourcePropertiesForOrphan as Yn, orphansCarriedFrom as Yr, configBooleanRefusal as Yt, green as Z, SynthesisError as Za, CFN_TEMPLATE_URL_LIMIT as Zi, refuseMalformedState as Zn, AssetPublisher as Zr, configStringRefusal as Zt, bagHoldsSecretExpression as _, hasPlainPrototype$1 as _a, resolveDockerContextDirectory as _i, INTRINSIC_KEYS as _n, dynamicReferenceTokens as _r, maskerOrIdentity as _t, DeploymentEventsStore as a, producerRecordKey as aa, BOOTSTRAP_MARKER_PREFIX as ai, withSharedDrainBudget as an, isRetryableTransientError as ao, findActionableSilentDrops as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, absoluteAssemblyPathEscape as ba, describeDockerFailure as bi, UNREADABLE_RESOURCES_MAP_ROW as bn, isSingleDynamicReferenceToken as br, interruptWatchListenerCount as bt, replayFailedOperations as c, PARTITION_TABLE as ca, getBootstrapMarkerKey as ci, s3BucketDualStackDomainName as cn, markNonRetryable as co, withRetry as cr, IAMRoleProvider as ct, updatePartialMessage as d, AssemblyReader as da, readBootstrapMarkerBody as di, applyRoleArnIfSet as dn, describeAwsFailure as do, STATE_SOURCED_BASELINE_RULES as dr, definedAttributes as dt, uploadCfnTemplate as ea, buildAssetRedirectMap as ei, requireConfigArray as en, normalizeAwsError as eo, repairMalformedResourceEntriesForReadOnly as er, collectDeclaredOutputNames as et, updatePartialReason as f, failedStageNote as fa, validateAssetBucketName as fi, AWS_CREDENTIAL_ENV_KEYS as fn, safeStringify$1 as fo, STATE_SOURCED_CROSS_GENERATION_RULES as fr, stringifyIfAssigned as ft, computeImplicitDeleteEdges as g, hasOwnKey as ga, buildDockerImage as gi, DiffCalculator as gn, createSecretMasker as gr, maskDeep as gt, IMPLICIT_DELETE_DEPENDENCIES as h, defineOwnKey as ha, assertCdkLocalDockerContextContained as hi, envCredentialsAreAssumedRole as hn, carriesSecretMask as hr, createMaskedRetryLogger as ht, DeploymentEventsReader as i, producerCoordinateKey as ia, AssetModeResolver as ii, producerRegionsFromState as in, isMarkedNonRetryable as io, safeStackName as ir, secretBearing as it, isFinalSnapshotError as j, AssetError as ja, warnFinchArgvExposure as ji, malformedOrphansWarning as jn, LockManager as jr, IntrinsicFunctionResolver as jt, ccRoutedFinalSnapshotError as k, runWithStackAwsClients as ka, runDockerForeground as ki, malformedOrphanRecordsWarning as kn, redactSecretsForState as kr, disableInstanceApiTermination as kt, replayRollback as l, canonicalizeRegion as la, isCrossRegionRedirect as li, s3BucketRegionalDomainName as ln, markRedactedCause as lo, DagBuilder as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, processStackMessages as ma, buildDenyExternalAccessPolicy as mi, callerEnvCredentials as mn, TEMPLATE_SOURCED_RULES as mr, wouldReturnToSdkProvider as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, injectiveKey as na, loadPublishableAssetManifest as ni, requireConfigString as nn, pasteableCommand as no, repairMalformedResourcesForReadOnly as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, decodeListingKey as oa, assertAssetBucketRegion as oi, s3BucketArn as on, isThrottlingError as oo, findSilentDropProperties as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, globMatches as pa, validateContainerRepoName as pi, applyCallerIdentityCredentials as pn, __exportAll as po, STATE_SOURCED_READBACK_RULES as pr, ProviderRegistry as pt, formatResourceLine as q, ResourceUpdateNotSupportedError as qa, stateBucketExistenceConfirmed as qi, refuseMalformedResourceEntries as qn, isReadableBag as qr, coerceCfnBoolean as qt, DeployEngine as r, injectiveKeyPrefix as ra, rewriteTemplateAssetReferences as ri, classifyReplaySecretRegion as rn, shellQuote as ro, safeRegion as rr, isExportAliasCollision as rt, planRollback as s, expectedOwnerParam as sa, ensureAssetStorage as si, s3BucketDomainName as sn, isTransientServerError as so, describeTypeWithThrottleRetry as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as ta, createAssetRedirectResolver as ti, requireConfigObject as tn, withErrorHandling as to, repairMalformedResourcePropertiesForReadOnly as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, derivePartitionAndUrlSuffix as ua, parseBootstrapMarker as ui, s3BucketWebsiteUrl as un, retryClassificationText as uo, TemplateParser as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, nullPrototypeRecord as va, describeDockerCapturedOutput as vi, STATE_RESOURCES_MALFORMED as vn, errorCauseChain as vr, beginCommandInterruptScope as vt, reachableDivergentParameters as w, clearBucketRegionCache as wa, getDockerCmd as wi, hasReadableResources as wn, maskSecretsInText as wr, slowCcOperationTimeoutMs as wt, bindingSkippedOutputs as x, displayAssemblyPath as xa, dockerSpawnEnvWithSensitive as xi, displayLogicalId as xn, isUncertifiedBaselineMaskPosition as xr, isInterruptedWaitError as xt, keptWholeReasonText as y, ownValue as ya, describeDockerExecFailure as yi, UNREADABLE_ORPHANS_CONTAINER_ROW as yn, identityKeyFor as yr, endCommandInterruptScope as yt, isStatefulRecreateTargetForReplace as z, LocalInvokeBuildError as za, getLegacyStateBucketName as zi, refuseDivergentRecordRegionForDestroy as zn, CUSTOM_RESOURCE_RESPONSE_PREFIX as zr, isUnboundTemplateParameter as zt };
51813
- //# sourceMappingURL=deploy-engine-xxVOQPPr.js.map
52400
+ export { yellow as $, MalformedProducerRecordRefusalError as $a, resolveSkipPrefix as $i, refuseMalformedOutputsForDestroy as $n, hasReadableExportSet as $r, readConfigString as $t, createPreDeleteFinalSnapshot as A, ownValue as Aa, describeDockerFailure as Ai, malformedExportSourceWarning as An, identityKeyFor as Ar, disableInstanceApiTermination as At, isStatefulRecreateTargetSync as B, runWithStackAwsClients as Ba, runDockerStreaming as Bi, malformedResourceEntriesWarning as Bn, redactSecretsForState as Br, isUnboundTemplateParameter as Bt, namesAnyDeclaredParameter as C, failedStageNote as Ca, validateAssetBucketName as Ci, displayLogicalId as Cn, safeStringify$1 as Co, STATE_SOURCED_CROSS_GENERATION_RULES as Cr, CloudControlProvider as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, hasOwnKey as Da, resolveDockerContextDirectory as Di, isReadableOrphanRecord as Dn, createSecretMasker as Dr, deleteIndeterminateGuards as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, defineOwnKey as Ea, buildDockerImage as Ei, hasReadableResources as En, carriesSecretMask as Er, UNSPECIFIED_SKIP_REASON as Et, extractDeploymentEventError as F, clearBucketRegionCache as Fa, isDockerClientEnvKey as Fi, malformedOrphansRefusalMessage as Fn, maskSecretsInText as Fr, cfnRefValueFromPhysicalId as Ft, toCfnInteger as G, CrossAccountSecretRefusalError as Ga, getDockerImageBySourceHash as Gi, refuseDivergentRecordRegionForDestroy as Gn, UNREPRODUCIBLE_LOCK_CLAUSE as Gr, normalizeAwsTagsToCfn as Gt, WARM_THROUGHPUT_MEMBERS as H, AssetError as Ha, escapeRegExp$1 as Hi, malformedResourcesWarning as Hn, LockManager as Hr, refStateLookupFromResource as Ht, makeSiblingClaimReader as I, resolveBucketRegion as Ia, isMalformedEnvKey as Ii, malformedOrphansWarning as In, pathCrossesDottedKey as Ir, coerceParameterTypedValue as It, bold as J, DynamicReferenceRegionAmbiguousError as Ja, getDefaultStateBucketName as Ji, refuseMalformedOrphanRecordsForDestroy as Jn, forceQuitRecoveryClause as Jr, coerceCfnBoolean as Jt, toFiniteNumber as K, DependencyError as Ka, Synthesizer as Ki, refuseMalformedNestedChildOutputs as Kn, buildForceUnlockCommand as Kr, resolveExplicitPhysicalId as Kt, planOrphanAdoption as L, AwsClients as La, partitionSensitiveEnv as Li, malformedOutputsRefusalMessage as Ln, recordMaskOnlyValue as Lr, getAccountInfo as Lt, refusesFinalSnapshot as M, displayAssemblyPath as Ma, finchSecretArgvRefusal as Mi, malformedOrphanRecordsRefusalMessage as Mn, isUncertifiedBaselineMaskPosition as Mr, IntrinsicFunctionResolver as Mt, unsupportedFinalSnapshotError as N, renderAssemblyPathEscape as Na, formatDockerLoginError as Ni, malformedOrphanRecordsWarning as Nn, markSameGenerationBag as Nr, carriesDynamicReference as Nt, buildFinalSnapshotIdentifier as O, hasPlainPrototype$1 as Oa, describeDockerCapturedOutput as Oi, isReadableResourceEntry as On, dynamicReferenceTokens as Or, deleteSkipReason as Ot, makeCanonicalizePropertiesFn as P, resolveAssemblyPath as Pa, getDockerCmd as Pi, malformedOrphanRowsKeptWarning as Pn, maskSecretsInError as Pr, carriesFetchableDynamicReference as Pt, red as Q, LockError as Qa, resolveCaptureObservedState as Qi, refuseMalformedOutputs as Qn, exportNamesCarriedFrom as Qr, configStringRefusal as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, getAwsClients as Ra, redactDockerArgvValues as Ri, malformedOutputsWarning as Rn, recordMaskOnlyValuesIn as Rr, isImpossibleEmptyStoredAttribute as Rt, computeParameterTaint as S, AssemblyReader as Sa, readBootstrapMarkerBody as Si, deployRefusesOrphanRowsReason as Sn, describeAwsFailure as So, STATE_SOURCED_BASELINE_RULES as Sr, startInterruptWatch as St, resourcesNamingDeclaredParameter as T, processStackMessages as Ta, buildDenyExternalAccessPolicy as Ti, hasReadableOutputs as Tn, TEMPLATE_SOURCED_RULES as Tr, ccProtectionRegistryTypes as Tt, coerceWarmThroughput as U, CdkdError as Ua, stripControlChars as Ui, malformedStateRefusalMessage as Un, S3StateBackend as Ur, isSettledInstanceState as Ut, renderStatefulReason as V, setAwsClients as Va, warnFinchArgvExposure as Vi, malformedResourcePropertiesWarning as Vn, scrubResourceRecord as Vr, parameterTypeMayLoseSecretIdentity as Vt, isWarmThroughputDecrease as W, ConfigError as Wa, AssetManifestLoader as Wi, previewableOrphanRecords as Wn, rebuildClientForBucketRegion as Wr, WAFv2WebACLProvider as Wt, gray as X, LocalInvokeBuildError as Xa, resolveApp as Xi, refuseMalformedOrphansForDestroy as Xn, CUSTOM_RESOURCE_RESPONSE_PREFIX as Xr, configBooleanRefusal as Xt, cyan as Y, IntrinsicResolutionRefusalError as Ya, getLegacyStateBucketName as Yi, refuseMalformedOrphans as Yn, formatLockExpiry as Yr, coerceCfnInteger as Yt, green as Z, LocalStartServiceError as Za, resolveAutoAssetStorage as Zi, refuseMalformedOrphansForOrphan as Zn, DEFAULT_STATE_PREFIX as Zr, configIntegerRefusal as Zt, bagHoldsSecretExpression as _, expectedOwnerParam as _a, assertAssetBucketRegion as _i, DiffCalculator as _n, isThrottlingError as _o, findSilentDropProperties as _r, maskerOrIdentity as _t, DeploymentEventsStore as a, ssmResolvedValueType as aa, orphansAfterRollback as ai, producerRegionsFromState as an, StackHasActiveImportsError as ao, refuseMalformedState as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, derivePartitionAndUrlSuffix as ba, isCrossRegionRedirect as bi, UNREADABLE_ORPHANS_CONTAINER_ROW as bn, markRedactedCause as bo, DagBuilder as br, interruptWatchListenerCount as bt, replayFailedOperations as c, MIGRATE_TMP_PREFIX as ca, AssetPublisher as ci, s3BucketDomainName as cn, SynthesisError as co, repairMalformedOutputsForReadOnly as cr, IAMRoleProvider as ct, updatePartialMessage as d, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as da, buildAssetRedirectMap as di, s3BucketWebsiteUrl as dn, normalizeAwsError as do, repairMalformedResourcesForReadOnly as dr, definedAttributes as dt, resolveStateBucketWithDefault as ea, hasReasonlessBaselineRefusal as ei, replayWarn as en, NestedStackChildDirectDestroyError as eo, refuseMalformedResourceAttributesForOrphan as er, collectDeclaredOutputNames as et, updatePartialReason as f, injectiveKey as fa, createAssetRedirectResolver as fi, applyRoleArnIfSet as fn, withErrorHandling as fo, safeRegion as fr, stringifyIfAssigned as ft, computeImplicitDeleteEdges as g, decodeListingKey as ga, BOOTSTRAP_MARKER_PREFIX as gi, envCredentialsAreAssumedRole as gn, isRetryableTransientError as go, findActionableSilentDrops as gr, maskDeep as gt, IMPLICIT_DELETE_DEPENDENCIES as h, producerRecordKey as ha, AssetModeResolver as hi, callerEnvCredentials as hn, isMarkedNonRetryable as ho, unreadableOrphanRecords as hr, createMaskedRetryLogger as ht, DeploymentEventsReader as i, warnDeprecatedNoPrefixCliFlag as ia, isReadableBag as ii, classifyReplaySecretRegion as in, ResourceUpdateNotSupportedError as io, refuseMalformedResourcesForDestroy as ir, secretBearing as it, isFinalSnapshotError as j, absoluteAssemblyPathEscape as ja, dockerSpawnEnvWithSensitive as ji, malformedLocalOutputsWarning as jn, isSingleDynamicReferenceToken as jr, isTerminationProtectionPropagationError as jt, ccRoutedFinalSnapshotError as k, nullPrototypeRecord as ka, describeDockerExecFailure as ki, malformedExportNamesWarning as kn, errorCauseChain as kr, isWaitAbandonedError as kt, replayRollback as l, findLargeInlineResources as la, stringifyValue as li, s3BucketDualStackDomainName as ln, formatError as lo, repairMalformedResourceEntriesForReadOnly as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, producerCoordinateKey as ma, rewriteTemplateAssetReferences as mi, applyCallerIdentityCredentials as mn, shellQuote as mo, unpreviewableOrphanRecords as mr, wouldReturnToSdkProvider as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, resolveUseCdkBootstrapAssets as na, importableOutputKeys as ni, requireConfigObject as nn, ProvisioningError as no, refuseMalformedResourceEntriesForOrphan as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, CFN_TEMPLATE_BODY_LIMIT as oa, orphansCarriedFrom as oi, withSharedDrainBudget as on, StackTerminationProtectionError as oo, repairMalformedOrphanRecordsForReadOnly as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, injectiveKeyPrefix as pa, loadPublishableAssetManifest as pi, AWS_CREDENTIAL_ENV_KEYS as pn, pasteableCommand as po, safeStackName as pr, ProviderRegistry as pt, formatResourceLine as q, DeployCancelledError as qa, synthesisStatusMessage as qi, refuseMalformedOrphanRecords as qn, buildLockContentionMessage as qr, assertRegionMatch as qt, DeployEngine as r, stateBucketExistenceConfirmed as ra, importableOutputs as ri, requireConfigString as rn, ResourceTimeoutError as ro, refuseMalformedResourcePropertiesForOrphan as rr, isExportAliasCollision as rt, planRollback as s, CFN_TEMPLATE_URL_LIMIT as sa, shouldRetainResource as si, s3BucketArn as sn, StateError as so, repairMalformedOrphansForReadOnly as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, resolveStateBucketWithDefaultAndSource as ta, hasUnverifiableParameterRefusal as ti, requireConfigArray as tn, PartialFailureError as to, refuseMalformedResourceEntries as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, uploadCfnTemplate as ua, WorkGraph as ui, s3BucketRegionalDomainName as un, isCdkdError as uo, repairMalformedResourcePropertiesForReadOnly as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, PARTITION_TABLE as va, ensureAssetStorage as vi, INTRINSIC_KEYS as vn, isTransientServerError as vo, describeTypeWithThrottleRetry as vr, beginCommandInterruptScope as vt, reachableDivergentParameters as w, globMatches as wa, validateContainerRepoName as wi, hasReadableOrphans as wn, __exportAll as wo, STATE_SOURCED_READBACK_RULES as wr, slowCcOperationTimeoutMs as wt, bindingSkippedOutputs as x, ambientClientDefaults as xa, parseBootstrapMarker as xi, UNREADABLE_RESOURCES_MAP_ROW as xn, retryClassificationText as xo, TemplateParser as xr, isInterruptedWaitError as xt, keptWholeReasonText as y, canonicalizeRegion as ya, getBootstrapMarkerKey as yi, STATE_RESOURCES_MALFORMED as yn, markNonRetryable as yo, withRetry as yr, endCommandInterruptScope as yt, isStatefulRecreateTargetForReplace as z, resetAwsClients as za, runDockerForeground as zi, malformedRenderedContainersWarning as zn, recoverMaskedOutput as zr, isNamelessDynamicReferenceError as zt };
52401
+ //# sourceMappingURL=deploy-engine-OgrMdBRR.js.map