@go-to-k/cdkd 0.291.4 → 0.291.5

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 { $a as describeAwsFailure, Dt as disableInstanceApiTermination, Fa as ProvisioningError, Ht as normalizeAwsTagsToCfn, La as ResourceUpdateNotSupportedError, Pr as shellQuote, Wt as assertRegionMatch, to as __exportAll, ut as clearOnUpdateRemoval } from "./deploy-engine-CpMT9Bmm.js";
1
+ import { Dt as disableInstanceApiTermination, Ht as normalizeAwsTagsToCfn, Ia as ProvisioningError, Pr as shellQuote, Ra as ResourceUpdateNotSupportedError, Wt as assertRegionMatch, eo as describeAwsFailure, no as __exportAll, ut as clearOnUpdateRemoval } from "./deploy-engine-BtJocpRR.js";
2
2
  import { T as displaySafe, n as getLogger, p as generateResourceName } from "./logger-GASr9bAF.js";
3
- import { n as awsClientDefaults } from "./aws-client-defaults-BoIxyCir.js";
3
+ import { n as awsClientDefaults, s as ambientRegion } from "./aws-client-defaults-jISU4PHo.js";
4
4
  import { EC2Client } from "@aws-sdk/client-ec2";
5
5
  import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
6
6
 
@@ -221,7 +221,7 @@ var asg_provider_exports = /* @__PURE__ */ __exportAll({ ASGProvider: () => ASGP
221
221
  var ASGProvider = class ASGProvider {
222
222
  asgClient;
223
223
  ec2Client;
224
- providerRegion = process.env["AWS_REGION"];
224
+ providerRegion = ambientRegion();
225
225
  logger = getLogger().child("ASGProvider");
226
226
  handledProperties = /* @__PURE__ */ new Map([["AWS::AutoScaling::AutoScalingGroup", /* @__PURE__ */ new Set([
227
227
  "AutoScalingGroupName",
@@ -1020,4 +1020,4 @@ function mapNotificationsToCfn(configurations) {
1020
1020
 
1021
1021
  //#endregion
1022
1022
  export { renderDisableCommand as a, protectedReplacementAdvice as i, asg_provider_exports as n, UNNAMEABLE_ID_CLAUSE as r, ASGProvider as t };
1023
- //# sourceMappingURL=asg-provider-_vdcb9QG.js.map
1023
+ //# sourceMappingURL=asg-provider-CZTUKQnY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asg-provider-CZTUKQnY.js","names":["DeleteTagsCommand"],"sources":["../src/provisioning/replacement-protection-advice.ts","../src/provisioning/providers/asg-provider.ts"],"sourcesContent":["/**\n * The sentence a provider's replacement advice must say instead of a bare\n * \"re-deploy with `--replace`\" when cdkd's own properties already say the\n * resource carries a deletion / termination protection flag.\n *\n * Issue [#2579](https://github.com/go-to-k/cdkd/issues/2579) established the\n * shape on `AWS::Logs::LogGroup`: the advised replacement runs its DELETE from\n * the deploy engine, which never sets `DeleteContext.removeProtection` — every\n * provider's flip-off is gated on that field and only the DESTROY paths\n * (`cdkd destroy`, `cdkd state destroy`) set it, because `cdkd deploy`\n * registers no `--remove-protection` Option at all (`deployOptions` in\n * `src/cli/options.ts`, fenced by\n * `tests/unit/cli/replacement-remedy-cli-facts.test.ts` against the real\n * Commander tree). So AWS refuses the delete and the command cdkd just told the\n * user to run dies on a SECOND wall with nothing named to do about it.\n *\n * Issue [#2610](https://github.com/go-to-k/cdkd/issues/2610) swept that shape\n * across the tree and found the same dead end on five more providers. This\n * module is the shared definition those five sites use, so the mechanism is\n * stated once rather than paraphrased five times — the reason\n * `lock-contention-message.ts` exists one directory over, and the reason\n * `display-safe.ts`'s header gives for not widening a rule by hand.\n *\n * `docs/cli-deploy-safety.md`'s \"Deletion protection blocks a replacement\"\n * section names all six types whose refusal says this explicitly (issue\n * [#2658](https://github.com/go-to-k/cdkd/issues/2658)), including the two\n * exceptions worth knowing before adding a seventh: ASG's arm is narrower than\n * its protection setting, and Cognito's fires after the desired bag is applied.\n *\n * `logs-loggroup-provider.ts` does NOT route through it and keeps its own text:\n * three review rounds on #2579 shaped clauses that are true only of that type\n * (`--force-stateful-recreation` has no per-resource granularity;\n * `LogGroupName` is the type's only replacement property, so a same-name\n * `Retain` replacement cannot complete at all), and a `suffix` parameter wide\n * enough to carry them would make the shared part the smaller half. Its message\n * is pinned by its own tests; this one by\n * `tests/unit/provisioning/replacement-remedy-preconditions.test.ts`.\n *\n * **Read that as a decision about the TEXT, never about the id rendering.**\n * Since issue [#2669](https://github.com/go-to-k/cdkd/issues/2669) that site\n * renders its command through the EXPORTED {@link renderDisableCommand} —\n * the same sanitize / quote / suppress the five callers above get, with the\n * log-group text kept around it — and falls back to\n * {@link UNNAMEABLE_ID_CLAUSE} when the id is suppressed.\n * `tests/integration/loggroup-class-guard/verify.sh` greps the rendered\n * command, so its needle is the UNQUOTED form `shellQuote` produces for a\n * clean id.\n *\n * **The export is no longer for \"exactly one caller\".** Issue\n * [#3136](https://github.com/go-to-k/cdkd/issues/3136) found the same\n * hand-quoted `'${value}'` shape at four more sites that are NOT protected-arm\n * remedies at all — two partial-create cleanup warnings\n * (`ssm-parameter-provider.ts`, `s3-bucket-provider.ts`, the latter sharing one\n * clause between its two arms) and one import refusal\n * (`ssm-parameter-provider.ts`'s `refuseUnwritableParameterId`) — so the right\n * question for a NEW caller is not \"can it use\n * {@link protectedReplacementAdvice} instead\" but \"is the value it pastes\n * cdkd-MINTED with a proven charset\". Where it is not, it belongs here.\n * `efs-provider.ts`'s `aws efs describe-access-points --query\n * \"AccessPoints[?ClientToken=='<token>']\"` is the recorded counter-example and\n * deliberately does NOT route through this: the token comes from\n * `acquireIdempotencyToken`, which mints `cdkd-<sha256 hex>` and nothing else,\n * and those single quotes are JMESPath string delimiters INSIDE a\n * double-quoted shell argument — `shellQuote`'s escaping would produce invalid\n * JMESPath rather than safer shell.\n *\n * Two properties every caller owes, because only the caller can answer them:\n *\n * - **`evidence` must name the BAG it read.** Most read the RECORDED bag\n * (`previousProperties`), which on the deploy path is\n * `ResourceState.properties` — cdkd's best account of what AWS holds on the\n * resource that is about to be deleted. A caller whose refusal fires AFTER\n * the desired bag has been applied (`CognitoUserPoolProvider`'s schema guard\n * runs after `UpdateUserPool`) must read the desired value first and say so.\n * Protection enabled OUT OF BAND is in no bag at all, so such a resource\n * still gets the short advice and still hits the wall — cdkd cannot see what\n * it never recorded, and probing AWS from a refusal path would add a call to\n * the failure route. Interpolate no unsanitized value into it: it is rendered\n * to a terminal and captured into `deployments/*.jsonl`.\n * - **`disable.command` must change nothing but the flag.** Where a service's\n * update API may take other members down with the flag, say so through\n * `disable.caveat` — rendered OUTSIDE the backticks, so the pasteable span\n * stays pasteable.\n *\n * **State only what has been measured, per field.** This text used to assert\n * that Cognito's `UpdateUserPool` \"resets every member it is not sent\" —\n * AWS's own blanket wording, which `cognito-provider.ts`'s\n * `readLiveMfaConfiguration` ledger shows holds FIELD BY FIELD and is FALSE\n * for at least two (`MfaConfiguration` and both `Policies` sub-keys survive\n * omission; `AutoVerifiedAttributes` does not). Issue\n * [#2610](https://github.com/go-to-k/cdkd/issues/2610)'s review retired that\n * universal from the user-facing caveat, and it survived HERE for a round —\n * which is the failure mode worth naming, because this is the normative text\n * a future caller copies. A blanket \"resets to default\" claim is per-field\n * and needs a per-field measurement; `DeletionProtection`'s (it does NOT\n * reset) is issue [#2675](https://github.com/go-to-k/cdkd/issues/2675).\n */\n\nimport { displaySafe } from '../utils/display-safe.js';\nimport { shellQuote } from '../state/lock-contention-message.js';\n\n/**\n * The doc section every caller points at. A single constant so a rename of the\n * heading cannot leave five providers pointing at a section that no longer\n * exists; `tests/unit/provisioning/replacement-remedy-preconditions.test.ts`\n * resolves it against `docs/cli-deploy-safety.md`'s actual headings.\n */\nexport const DELETION_PROTECTION_DOC_POINTER =\n '\"Deletion protection blocks a replacement, and deploy cannot clear it\" in docs/cli-deploy-safety.md';\n\n/** What the message says when the resource id cannot be named on a command line. */\nexport const UNNAMEABLE_ID_CLAUSE =\n 'Then disable protection out of band, via the console: the physical id cdkd recorded for this ' +\n 'resource cannot be reproduced safely on a command line, so any command shown here would act on ' +\n 'a different resource.';\n\n/**\n * A string the COMPILER can prove is a literal.\n *\n * Two shapes are rejected, and the second is the one a plain\n * `string extends T` test misses:\n *\n * - a WIDENED `string` -- what every non-literal expression produces:\n * `'lit ' + String(x)`, a `const` declared `: string`, a value read out of a\n * builder's return type, a spread of any of those, and (deliberately) a\n * concatenation of two literals;\n * - a TEMPLATE LITERAL TYPE carrying an interpolation. `` `aws x ${derived}` ``\n * does NOT widen to `string` -- its type is `` `aws x ${string}` ``, so\n * `string extends T` is FALSE and a one-line constraint accepts it, while\n * the interpolated span is exactly the attacker-controlled text this module\n * exists to keep out of a pasteable command. `HasInterpolation` walks the\n * type one character at a time and reports any tail that IS `string`, which\n * is what a `${...}` hole leaves behind. Measured on the longest literal in\n * use (203 characters): no depth error, whole-project typecheck unchanged.\n *\n * Both resolve to `never` and fail to assign, at `vp run typecheck`.\n *\n * **Why a TYPE and not a test.** This constraint replaced a source-text sniff\n * over `src/` that was three spellings deep by the time issue [#2610]'s fifth\n * review round reached it: the first cut tested only a value's opening\n * character; the second joined continuation lines only until the value first\n * looked literal, so Prettier's operator-at-line-start layout walked through;\n * the third derived its population from field names near an `identifier`, which\n * a builder returning `{ before, after }` from another module simply exits.\n * Each hole was closed and the next one was found by the next probe. A grep\n * cannot see a type, so the honest fix is to stop grepping: a widened `string`\n * is now a COMPILE ERROR, which no refactor, spread, shorthand or far-away\n * `const` can walk out of. `tests/unit/cli/replacement-remedy-cli-facts.test.ts`\n * pins one `@ts-expect-error` per evasion shape instead.\n *\n * The one shape it also rejects is a concatenation of two LITERALS\n * (`'a' + 'b'` widens to `string` in TypeScript). That is a real cost, paid\n * deliberately: a single literal is what the fence can see, and the one caller\n * that concatenated was rewritten to use one.\n */\ntype HasInterpolation<T extends string> = string extends T\n ? true\n : T extends `${infer _Head}${infer Rest}`\n ? string extends Rest\n ? true\n : HasInterpolation<Rest>\n : false;\n\nexport type CdkdAuthoredLiteral<T extends string> = HasInterpolation<T> extends true ? never : T;\n\n/**\n * The out-of-band disable command, split so only the IDENTIFIER is untrusted.\n *\n * `before` / `after` / `caveat` are rendered VERBATIM (inside the backticks for\n * the first two, beside them for the third), so they must be cdkd-authored\n * literals — the compiler now enforces that. `identifier` is the opposite: it\n * is a `state.json` value, and {@link protectedReplacementAdvice} sanitizes,\n * shell-quotes and suppresses it.\n */\nexport interface ProtectedReplacementDisableCommand<\n Before extends string = string,\n After extends string = string,\n Caveat extends string = string,\n> {\n /**\n * Everything before the resource id, e.g.\n * `aws elbv2 modify-load-balancer-attributes --load-balancer-arn`.\n */\n before: CdkdAuthoredLiteral<Before>;\n /**\n * The resource's physical id. SANITIZED and SHELL-QUOTED here, and the whole\n * command is SUPPRESSED when sanitizing changes it — see\n * {@link protectedReplacementAdvice}.\n */\n identifier: string;\n /**\n * Everything after the id, e.g.\n * `--attributes Key=deletion_protection.enabled,Value=false`.\n */\n after?: CdkdAuthoredLiteral<After>;\n /**\n * Rendered as a sentence AFTER the command, never inside its backticks — it\n * reaches the terminal and the persisted events store like everything else\n * here.\n */\n caveat?: CdkdAuthoredLiteral<Caveat>;\n /**\n * The caller's secret masker, when it has one. Optional: most callers render\n * a physical id read off `state.json` and have none.\n *\n * It exists because `shellQuote` can put a SECRET past the message-level\n * mask, which is a hazard this module created and only this module can\n * close (found by the security review of issue\n * [#3136](https://github.com/go-to-k/cdkd/issues/3136)). `maskSecretsInText`\n * matches by LITERAL occurrence, and `shellQuote` rewrites every inner `'`\n * to `'\\''` — so a resolved `{{resolve:secretsmanager:...}}` value\n * containing a quote no longer OCCURS in the finished line and comes through\n * a masking sink in PLAINTEXT. The hand-quoted `'${value}'` this module\n * replaced did not have that problem, because it left the value\n * byte-identical.\n *\n * Supplying it makes {@link renderDisableCommand} SUPPRESS the command for\n * any id the masker would change — the same answer sanitization already\n * gets, for the same reason: a command cdkd cannot show honestly is better\n * not shown. It is deliberately NOT applied to the rendered command, which\n * would only reintroduce the escaping problem one layer up.\n */\n maskSecrets?: (text: string) => string;\n}\n\n/** The same shape with the literal constraint discharged, for internal use. */\ninterface ResolvedDisableCommand {\n before: string;\n identifier: string;\n after?: string;\n caveat?: string;\n maskSecrets?: (text: string) => string;\n}\n\nexport interface ProtectedReplacementAdviceArgs<\n Before extends string = string,\n After extends string = string,\n Caveat extends string = string,\n> {\n /**\n * A complete clause naming WHAT cdkd read and WHERE, with no trailing\n * punctuation — e.g. `\"cdkd's recorded properties for this cluster carry\n * Instances.TerminationProtected: true\"`. It is the first half of the\n * returned sentence, so it must read as a subject + verb.\n */\n evidence: string;\n /**\n * The flags the SHORT advice at this site names, spelled exactly as the user\n * would type them (e.g. `'cdkd deploy --replace --force-stateful-recreation'`).\n * Named twice in the output — once in the refusal and once in the re-run — so\n * the two can never drift.\n */\n replaceFlags: string;\n /** A concrete out-of-band way to turn protection off. */\n disable: ProtectedReplacementDisableCommand<Before, After, Caveat>;\n}\n\n/**\n * Render `disable` as a pasteable command, or `''` when its id cannot be named.\n *\n * The id reaching here is NOT an AWS-minted literal, which an earlier revision\n * of this module claimed and the review of issue [#2610] disproved: the deploy\n * engine passes `currentResource.physicalId` straight off `state.json`, and for\n * `AWS::DynamoDB::GlobalTable` and `AWS::AutoScaling::AutoScalingGroup` the\n * physical id is the TEMPLATE-chosen name. So it is exactly the class\n * `lock-contention-message.ts` handles, and it gets the identical treatment\n * rather than a second spelling of it:\n *\n * - sanitize FIRST (`asciiOnly`, a positive allowlist — a control character\n * has no legitimate place in an AWS physical id, and left in it forges lines\n * on the operator's terminal and inside `deployments/*.jsonl`);\n * - then `shellQuote`, the SAME predicate the force-unlock hint uses, so a\n * value carrying a quote or a space cannot truncate when pasted;\n * - and SUPPRESS the whole command when sanitizing CHANGED the value, because\n * a command naming the sanitized id would act on a DIFFERENT resource — the\n * wrong-target harm that module exists to prevent, and the reason emitting\n * no command is the honest answer rather than a fallback.\n */\nexport function renderDisableCommand<\n Before extends string,\n After extends string = string,\n Caveat extends string = string,\n>(disable: ProtectedReplacementDisableCommand<Before, After, Caveat>): string {\n // EXPORTED since issue [#2669] for the one caller that owns its own sentence\n // (`logs-loggroup-provider.ts`), and exported OVER THE CONSTRAINED shape:\n // the generic signature keeps `CdkdAuthoredLiteral` in force for a direct\n // caller exactly as {@link protectedReplacementAdvice} keeps it for the\n // five, so a template-derived `before` / `after` is a COMPILE error at both\n // entry points (`tests/unit/cli/replacement-remedy-cli-facts.test.ts` pins\n // the direct call). Exporting the discharged `ResolvedDisableCommand`\n // instead would have re-opened the door the constraint exists to close.\n const resolved = disable as ResolvedDisableCommand;\n const safeId = displaySafe(resolved.identifier, { asciiOnly: true });\n if (!safeId || safeId !== resolved.identifier) return '';\n // A SECRET-bearing id is suppressed for the same reason a sanitized one is:\n // see {@link ProtectedReplacementDisableCommand.maskSecrets} for why the\n // message-level mask cannot catch it once `shellQuote` has escaped a quote.\n // Only a caller that HAS a masker can ask this; the rest are unchanged.\n if (resolved.maskSecrets && resolved.maskSecrets(safeId) !== safeId) return '';\n const tail = resolved.after ? ` ${resolved.after}` : '';\n return `${resolved.before} ${shellQuote(safeId)}${tail}`;\n}\n\n/**\n * Build the \"your advised replacement is blocked by a protection flag\" advice.\n *\n * Deliberately STOPS at what a provider's `update()` can know. It sees neither\n * `UpdateReplacePolicy` nor what the replacement then does, and three review\n * rounds on issue [#2579] each proved a different sentence about that\n * downstream mechanism false — so the outcome of disabling is handed to the\n * doc, which has the policy in view. A shorter message that is TRUE beats a\n * complete one that is not.\n */\nexport function protectedReplacementAdvice<\n Before extends string,\n After extends string = string,\n Caveat extends string = string,\n>(args: ProtectedReplacementAdviceArgs<Before, After, Caveat>): string {\n const { evidence, replaceFlags } = args;\n // Rendered from the still-CONSTRAINED shape, so this call is fenced like a\n // direct one; the discharged view below is only for reading `caveat`.\n const command = renderDisableCommand(args.disable);\n const disable = args.disable as ResolvedDisableCommand;\n const head =\n `${evidence}, so ${replaceFlags} alone will NOT succeed while AWS still has protection on: ` +\n `the replacement DELETES the resource, AWS refuses that delete while protection is on, and ` +\n `cdkd deploy has no --remove-protection flag to clear it (only cdkd destroy and ` +\n `cdkd state destroy act on one). Read ${DELETION_PROTECTION_DOC_POINTER} BEFORE you disable ` +\n `anything: whether disabling helps at all, and what the flag ends up as, depend on your ` +\n `UpdateReplacePolicy and on whether the deploy completes — neither of which this refusal can ` +\n `see.`;\n if (!command) return `${head} ${UNNAMEABLE_ID_CLAUSE} Then re-run with ${replaceFlags}.`;\n const caveat = disable.caveat ? ` ${disable.caveat}` : '';\n return (\n `${head} Then disable protection out of band — \\`${command}\\`, or via the console.${caveat} ` +\n `Then re-run with ${replaceFlags}.`\n );\n}\n","import {\n AutoScalingClient,\n CreateAutoScalingGroupCommand,\n UpdateAutoScalingGroupCommand,\n DeleteAutoScalingGroupCommand,\n DescribeAutoScalingGroupsCommand,\n type DescribeAutoScalingGroupsCommandOutput,\n DescribeLifecycleHooksCommand,\n DescribeTrafficSourcesCommand,\n DescribeNotificationConfigurationsCommand,\n EnableMetricsCollectionCommand,\n DisableMetricsCollectionCommand,\n PutLifecycleHookCommand,\n DeleteLifecycleHookCommand,\n AttachTrafficSourcesCommand,\n DetachTrafficSourcesCommand,\n PutNotificationConfigurationCommand,\n DeleteNotificationConfigurationCommand,\n CreateOrUpdateTagsCommand,\n DeleteTagsCommand,\n AttachLoadBalancersCommand,\n DetachLoadBalancersCommand,\n AttachLoadBalancerTargetGroupsCommand,\n DetachLoadBalancerTargetGroupsCommand,\n type Tag as ASGTag,\n type LaunchTemplateSpecification,\n type AvailabilityZoneDistribution,\n type CapacityReservationSpecification,\n type DeletionProtection,\n type InstanceMaintenancePolicy,\n} from '@aws-sdk/client-auto-scaling';\nimport { EC2Client } from '@aws-sdk/client-ec2';\nimport { describeAwsFailure } from '../../utils/aws-failure-text.js';\nimport { getLogger } from '../../utils/logger.js';\nimport { ProvisioningError, ResourceUpdateNotSupportedError } from '../../utils/error-handler.js';\nimport { assertRegionMatch, type DeleteContext } from '../region-check.js';\nimport { disableInstanceApiTermination } from '../ec2-termination-protection.js';\nimport { generateResourceName } from '../resource-name.js';\nimport { normalizeAwsTagsToCfn } from '../import-helpers.js';\nimport type {\n ResourceProvider,\n ResourceCreateResult,\n ResourceUpdateResult,\n CreateContext,\n UpdateContext,\n SecretMasker,\n} from '../../types/resource.js';\nimport { clearOnUpdateRemoval } from '../update-removal.js';\nimport { protectedReplacementAdvice } from '../replacement-protection-advice.js';\nimport { awsClientDefaults } from '../../utils/aws-client-defaults.js';\nimport { ambientRegion } from '../../utils/stack-aws-scope.js';\n\n/**\n * AWS Auto Scaling Provider\n *\n * Implements resource provisioning for `AWS::AutoScaling::AutoScalingGroup`.\n *\n * WHY a dedicated SDK provider (instead of CC API fallback):\n * 1. Owns the `--remove-protection` flip-off: ASG protection has three\n * levels (`none` / `prevent-force-deletion` / `prevent-all-deletion`)\n * and the destroy path needs to (a) clear it via `UpdateAutoScalingGroup\n * ({DeletionProtection: 'none'})` before the actual delete and (b) set\n * `ForceDelete: true` on `DeleteAutoScalingGroup` so AWS terminates any\n * running instances as part of the delete (matches the user's \"I know\n * what I'm doing\" intent).\n * 2. Faster than CC API for the common case — direct Create/Update calls\n * with no eventual-consistency polling beyond what `DescribeAutoScaling\n * Groups` already provides.\n *\n * Update has narrower coverage than create: AWS does not support modifying\n * `AutoScalingGroupName` (immutable) — that diff still surfaces\n * `ResourceUpdateNotSupportedError` so the caller can `cdkd deploy\n * --replace`. The mutable fields handled in-place via\n * `UpdateAutoScalingGroup` include MinSize / MaxSize / DesiredCapacity /\n * VPCZoneIdentifier / HealthCheckType / HealthCheckGracePeriod /\n * DefaultCooldown / Cooldown / NewInstancesProtectedFromScaleIn /\n * MaxInstanceLifetime / TerminationPolicies / CapacityRebalance /\n * ServiceLinkedRoleARN / Context / DesiredCapacityType /\n * DefaultInstanceWarmup / AvailabilityZones / AvailabilityZoneDistribution\n * / AvailabilityZoneImpairmentPolicy / SkipZonalShiftValidation /\n * CapacityReservationSpecification / InstanceMaintenancePolicy /\n * DeletionProtection / MixedInstancesPolicy / LaunchTemplate.\n *\n * `UpdateAutoScalingGroup` has merge semantics (absent input field = \"no\n * change\"), so update() routes every optional mutable field through\n * `clearOnUpdateRemoval` — a property REMOVED from the template is reset to\n * its CFn default / SDK-documented clear sentinel, matching CloudFormation\n * (issue #1160).\n *\n * Sub-shape diffs are applied via dedicated AWS APIs before the main\n * `UpdateAutoScalingGroup` call:\n * - `Tags` → `CreateOrUpdateTags` / `DeleteTags` (#475)\n * - `LoadBalancerNames` → `AttachLoadBalancers` /\n * `DetachLoadBalancers` (#476)\n * - `TargetGroupARNs` → `AttachLoadBalancerTargetGroups` /\n * `DetachLoadBalancerTargetGroups` (#476)\n * - `MetricsCollection` → `EnableMetricsCollection` /\n * `DisableMetricsCollection`\n * - `LifecycleHookSpecificationList` → per-entry `PutLifecycleHook` /\n * `DeleteLifecycleHook`\n * - `TrafficSources` → `AttachTrafficSources` /\n * `DetachTrafficSources`\n * - `NotificationConfigurations` → per-topic\n * `PutNotificationConfiguration` /\n * `DeleteNotificationConfiguration`\n *\n * Each helper is a no-op when the before/after JSON is identical.\n */\nexport class ASGProvider implements ResourceProvider {\n private asgClient?: AutoScalingClient;\n private ec2Client?: EC2Client;\n private readonly providerRegion = ambientRegion();\n private logger = getLogger().child('ASGProvider');\n\n handledProperties = new Map<string, ReadonlySet<string>>([\n [\n 'AWS::AutoScaling::AutoScalingGroup',\n new Set([\n 'AutoScalingGroupName',\n 'LaunchTemplate',\n 'MinSize',\n 'MaxSize',\n 'DesiredCapacity',\n 'VPCZoneIdentifier',\n 'AvailabilityZones',\n 'HealthCheckType',\n 'HealthCheckGracePeriod',\n 'Cooldown',\n 'DefaultCooldown',\n 'Tags',\n 'TerminationPolicies',\n 'NewInstancesProtectedFromScaleIn',\n 'CapacityRebalance',\n 'ServiceLinkedRoleARN',\n 'MaxInstanceLifetime',\n 'LoadBalancerNames',\n 'TargetGroupARNs',\n 'MetricsCollection',\n 'LifecycleHookSpecificationList',\n 'MixedInstancesPolicy',\n 'Context',\n 'DesiredCapacityType',\n 'DefaultInstanceWarmup',\n 'TrafficSources',\n 'NotificationConfigurations',\n 'AvailabilityZoneDistribution',\n 'AvailabilityZoneImpairmentPolicy',\n 'SkipZonalShiftValidation',\n 'CapacityReservationSpecification',\n 'InstanceMaintenancePolicy',\n 'DeletionProtection',\n ]),\n ],\n ]);\n\n unhandledByDesign = new Map<string, ReadonlyMap<string, string>>([\n [\n 'AWS::AutoScaling::AutoScalingGroup',\n new Map<string, string>([\n [\n 'LaunchConfigurationName',\n 'AWS Launch Configurations end-of-life 2024-10; use LaunchTemplate instead',\n ],\n [\n 'NotificationConfiguration',\n 'Legacy singular form; use NotificationConfigurations (plural) which cdkd already wires',\n ],\n ]),\n ],\n ]);\n\n private getClient(): AutoScalingClient {\n if (!this.asgClient) {\n this.asgClient = new AutoScalingClient({\n ...awsClientDefaults(),\n ...(this.providerRegion ? { region: this.providerRegion } : {}),\n });\n }\n return this.asgClient;\n }\n\n private getEc2Client(): EC2Client {\n if (!this.ec2Client) {\n this.ec2Client = new EC2Client({\n ...awsClientDefaults(),\n ...(this.providerRegion ? { region: this.providerRegion } : {}),\n });\n }\n return this.ec2Client;\n }\n\n // ─── Dispatch ─────────────────────────────────────────────────────\n\n /**\n * The `context` parameter is read for ONE thing: `maskSecrets` (issue #1932\n * item 3, adopted here by issue #1997).\n *\n * An earlier revision of this comment claimed the create path had no site to\n * mask, on the grounds that it names \"only the logical id and the generated\n * group name\". Both halves were wrong: `groupName` falls back to a generated\n * name only when `AutoScalingGroupName` is ABSENT, so when the template\n * DECLARES it the value is a resolved property value — and it IS logged, on\n * the line below. Debug level is not an exemption; the contract's rule is any\n * log line that interpolates a value from the `properties` bag, and this\n * provider already masks its convergence-poll debug line for that reason.\n */\n async create(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n context?: CreateContext\n ): Promise<ResourceCreateResult> {\n if (resourceType !== 'AWS::AutoScaling::AutoScalingGroup') {\n throw new ProvisioningError(\n `Unsupported resource type: ${resourceType}`,\n resourceType,\n logicalId\n );\n }\n\n const groupName =\n (properties['AutoScalingGroupName'] as string | undefined) ||\n generateResourceName(logicalId, { maxLength: 255 });\n\n // ONE masked sink for this method's own lines (issue #1997).\n const maskSecrets: SecretMasker = context?.maskSecrets ?? ((text) => text);\n const debug = (message: string): void => this.logger.debug(maskSecrets(message));\n debug(`Creating AutoScalingGroup ${logicalId}: ${maskSecrets(groupName)}`);\n\n try {\n const launchTemplate = this.buildLaunchTemplate(properties);\n const tags = this.buildTags(groupName, properties);\n const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties['VPCZoneIdentifier']);\n\n const minSize = properties['MinSize'] != null ? Number(properties['MinSize']) : 0;\n const maxSize = properties['MaxSize'] != null ? Number(properties['MaxSize']) : minSize;\n\n await this.getClient().send(\n new CreateAutoScalingGroupCommand({\n AutoScalingGroupName: groupName,\n MinSize: minSize,\n MaxSize: maxSize,\n ...(properties['DesiredCapacity'] != null && {\n DesiredCapacity: Number(properties['DesiredCapacity']),\n }),\n ...(launchTemplate && { LaunchTemplate: launchTemplate }),\n ...(properties['MixedInstancesPolicy'] !== undefined && {\n MixedInstancesPolicy: properties['MixedInstancesPolicy'] as never,\n }),\n ...(vpcZoneIdentifier !== undefined && { VPCZoneIdentifier: vpcZoneIdentifier }),\n ...(properties['AvailabilityZones'] !== undefined && {\n AvailabilityZones: properties['AvailabilityZones'] as string[],\n }),\n ...(properties['HealthCheckType'] !== undefined && {\n HealthCheckType: properties['HealthCheckType'] as string,\n }),\n ...(properties['HealthCheckGracePeriod'] != null && {\n HealthCheckGracePeriod: Number(properties['HealthCheckGracePeriod']),\n }),\n ...(properties['Cooldown'] != null && {\n DefaultCooldown: Number(properties['Cooldown']),\n }),\n ...(properties['DefaultCooldown'] != null && {\n DefaultCooldown: Number(properties['DefaultCooldown']),\n }),\n ...(properties['TerminationPolicies'] !== undefined && {\n TerminationPolicies: properties['TerminationPolicies'] as string[],\n }),\n ...(properties['NewInstancesProtectedFromScaleIn'] !== undefined && {\n NewInstancesProtectedFromScaleIn: properties[\n 'NewInstancesProtectedFromScaleIn'\n ] as boolean,\n }),\n ...(properties['CapacityRebalance'] !== undefined && {\n CapacityRebalance: properties['CapacityRebalance'] as boolean,\n }),\n ...(properties['ServiceLinkedRoleARN'] !== undefined && {\n ServiceLinkedRoleARN: properties['ServiceLinkedRoleARN'] as string,\n }),\n ...(properties['MaxInstanceLifetime'] != null && {\n MaxInstanceLifetime: Number(properties['MaxInstanceLifetime']),\n }),\n ...(properties['LoadBalancerNames'] !== undefined && {\n LoadBalancerNames: properties['LoadBalancerNames'] as string[],\n }),\n ...(properties['TargetGroupARNs'] !== undefined && {\n TargetGroupARNs: properties['TargetGroupARNs'] as string[],\n }),\n ...(properties['Context'] !== undefined && {\n Context: properties['Context'] as string,\n }),\n ...(properties['DesiredCapacityType'] !== undefined && {\n DesiredCapacityType: properties['DesiredCapacityType'] as string,\n }),\n ...(properties['DefaultInstanceWarmup'] != null && {\n DefaultInstanceWarmup: Number(properties['DefaultInstanceWarmup']),\n }),\n ...(properties['LifecycleHookSpecificationList'] !== undefined && {\n LifecycleHookSpecificationList: properties['LifecycleHookSpecificationList'] as never,\n }),\n ...(properties['TrafficSources'] !== undefined && {\n TrafficSources: properties['TrafficSources'] as never,\n }),\n ...(properties['AvailabilityZoneDistribution'] !== undefined && {\n AvailabilityZoneDistribution: properties['AvailabilityZoneDistribution'] as never,\n }),\n ...(properties['AvailabilityZoneImpairmentPolicy'] !== undefined && {\n AvailabilityZoneImpairmentPolicy: properties[\n 'AvailabilityZoneImpairmentPolicy'\n ] as never,\n }),\n ...(properties['SkipZonalShiftValidation'] !== undefined && {\n SkipZonalShiftValidation: properties['SkipZonalShiftValidation'] as boolean,\n }),\n ...(properties['CapacityReservationSpecification'] !== undefined && {\n CapacityReservationSpecification: properties[\n 'CapacityReservationSpecification'\n ] as never,\n }),\n ...(properties['InstanceMaintenancePolicy'] !== undefined && {\n InstanceMaintenancePolicy: properties['InstanceMaintenancePolicy'] as never,\n }),\n ...(properties['DeletionProtection'] !== undefined && {\n DeletionProtection: properties['DeletionProtection'] as never,\n }),\n ...(tags.length > 0 && { Tags: tags }),\n })\n );\n\n debug(`Successfully created AutoScalingGroup ${logicalId}: ${maskSecrets(groupName)}`);\n\n const arn = await this.fetchArn(groupName);\n const attributes: Record<string, unknown> = {};\n if (arn) attributes['Arn'] = arn;\n if (launchTemplate?.LaunchTemplateId) {\n attributes['LaunchTemplateID'] = launchTemplate.LaunchTemplateId;\n }\n return { physicalId: groupName, attributes };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to create AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n groupName,\n cause\n );\n }\n }\n\n /**\n * The `context` parameter is read for ONE thing today: `maskSecrets` (issue\n * #1932 item 3, adopted here by issue #1997). `applyTargetGroupArnsDiff` is\n * the only path in this provider that interpolates a RESOLVED property value\n * into a log line — its convergence timeout names the expected\n * `TargetGroupARNs` set — so it is the only one the masker is threaded into.\n *\n * `create()` takes the masker too — see its own doc for why the original\n * \"nothing to mask there\" reading was wrong.\n */\n async update(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n previousProperties: Record<string, unknown>,\n context?: UpdateContext\n ): Promise<ResourceUpdateResult> {\n if (resourceType !== 'AWS::AutoScaling::AutoScalingGroup') {\n throw new ProvisioningError(\n `Unsupported resource type: ${resourceType}`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n this.logger.debug(`Updating AutoScalingGroup ${logicalId}: ${physicalId}`);\n\n // Reject diffs on fields AWS does not support modifying via\n // UpdateAutoScalingGroup. The replacement-detection layer typically\n // catches AutoScalingGroupName changes earlier; this is defense-in-\n // depth + the only place to surface the equivalent error for\n // sub-resource fields the caller may reasonably expect to round-trip.\n const stringEq = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);\n if (!stringEq(properties['AutoScalingGroupName'], previousProperties['AutoScalingGroupName'])) {\n // Issue [#2610] site 8. `--replace` alone cannot succeed on a group whose\n // recorded `DeletionProtection` is `'prevent-all-deletion'` (see the\n // level analysis below for why that is the ONLY blocking level): the\n // replacement's DELETE runs from the deploy engine, which never sets\n // `DeleteContext.removeProtection` — `delete()` below gates BOTH its\n // flip-off and its `ForceDelete: true` on exactly that field. See\n // `../replacement-protection-advice.ts`.\n //\n // Read the RECORDED bag: this guard fires before any\n // `UpdateAutoScalingGroup` in this method, so AWS still holds what\n // `previousProperties` records.\n //\n // The value is a THREE-level string enum, and only the strictest level\n // blocks a deploy-side replacement. AWS's own wording: the setting\n // controls whether `DeleteAutoScalingGroup` is allowed \"according to the\n // specified protection level\" -- `prevent-force-deletion` withholds only\n // the FORCE delete, and the deploy engine's replacement issues\n // `ForceDelete: false` (see `delete()` below, where the flag rides\n // `context?.removeProtection`). So on `prevent-force-deletion` the delete\n // is not refused BECAUSE of protection, and telling the user to clear it\n // would name a remedy that fixes nothing -- the exact defect issue\n // [#2610] is about, reintroduced by an over-wide predicate. An ABSENT\n // value is the AWS-side default, which `readCurrentState` writes back as\n // the explicit `'none'` placeholder.\n const recordedProtection = previousProperties['DeletionProtection'];\n const remedy =\n recordedProtection === 'prevent-all-deletion'\n ? protectedReplacementAdvice({\n evidence:\n \"cdkd's recorded properties for this group carry \" +\n 'DeletionProtection: prevent-all-deletion',\n replaceFlags: 'cdkd deploy --replace',\n disable: {\n before: 'aws autoscaling update-auto-scaling-group --auto-scaling-group-name',\n identifier: physicalId,\n after: '--deletion-protection none',\n },\n })\n : 'Use cdkd deploy --replace to replace the group.';\n throw new ResourceUpdateNotSupportedError(\n resourceType,\n logicalId,\n 'AutoScalingGroupName is immutable on AWS — UpdateAutoScalingGroup does not accept a new name; the name is fixed at creation. ' +\n remedy\n );\n }\n try {\n // Sub-shape diffs are applied via separate per-shape SDK calls\n // BEFORE the main UpdateAutoScalingGroup. AWS does not expose these\n // fields on UpdateAutoScalingGroup, so each one rides its own\n // dedicated API. Each per-shape helper is a no-op when the\n // before/after JSON is identical.\n await this.applyTagsDiff(physicalId, properties['Tags'], previousProperties['Tags']);\n await this.applyLoadBalancerNamesDiff(\n physicalId,\n properties['LoadBalancerNames'],\n previousProperties['LoadBalancerNames']\n );\n await this.applyTargetGroupArnsDiff(\n physicalId,\n properties['TargetGroupARNs'],\n previousProperties['TargetGroupARNs'],\n context?.maskSecrets\n );\n await this.applyMetricsCollectionDiff(\n physicalId,\n properties['MetricsCollection'],\n previousProperties['MetricsCollection']\n );\n await this.applyLifecycleHooksDiff(\n physicalId,\n properties['LifecycleHookSpecificationList'],\n previousProperties['LifecycleHookSpecificationList']\n );\n await this.applyTrafficSourcesDiff(\n physicalId,\n properties['TrafficSources'],\n previousProperties['TrafficSources']\n );\n await this.applyNotificationConfigurationsDiff(\n physicalId,\n properties['NotificationConfigurations'],\n previousProperties['NotificationConfigurations']\n );\n\n const launchTemplate = this.buildLaunchTemplate(properties);\n const vpcZoneIdentifier = this.joinVpcZoneIdentifier(properties['VPCZoneIdentifier']);\n\n // issue #1160: `UpdateAutoScalingGroup` has merge semantics — an absent\n // input field means \"no change\" — while CloudFormation resets a property\n // REMOVED from the template to its default. Resolve every optional\n // mutable field through `clearOnUpdateRemoval` so a removal sends the\n // explicit CFn default (or the SDK-documented clear sentinel) instead of\n // silently keeping the old live value. Each reset value's doc basis is\n // noted inline (models_0.d.ts = the AWS SDK command/model doc).\n //\n // Deliberately NOT reset on removal:\n // - DesiredCapacity: CFn leaves current capacity unmanaged when the\n // property is absent (scaling policies own it) — leaving it\n // unchanged IS the CFn-parity behavior.\n // - MinSize / MaxSize: required properties, never removable.\n // - LaunchTemplate vs MixedInstancesPolicy, VPCZoneIdentifier vs\n // AvailabilityZones: mutually-exclusive pairs — a \"removal\" is\n // really a switch to the other member, which the API applies by\n // presence; pure removal of both is an invalid template.\n // - ServiceLinkedRoleARN: no documented clear sentinel; the default\n // is an account-specific service-linked-role ARN — leave unchanged.\n // - Context: SDK doc says \"Reserved.\" — leave unchanged.\n // - SkipZonalShiftValidation: transient per-request validation flag,\n // not persisted group config — nothing to reset.\n // - AvailabilityZoneImpairmentPolicy: DEFERRED — the SDK model\n // documents no default for `ImpairedZoneHealthCheckBehavior` (and\n // none for `ZonalShiftEnabled`), so a reset shape cannot be derived\n // without guessing; removal currently keeps the live value.\n //\n // Sub-field removal inside a KEPT config object (issue #1225 — the\n // #1160 bug class one level down) is deliberately passed through\n // verbatim here:\n // - InstanceMaintenancePolicy: a kept-but-partial object (one of the\n // two percentages dropped) is REJECTED by AWS — the SDK doc requires\n // \"Both MinHealthyPercentage and MaxHealthyPercentage must be\n // specified\". CloudFormation submits the same partial object, so the\n // loud failure IS the CFn-parity behavior; there is no silent drop\n // to normalize away.\n // - CapacityReservationSpecification: whether AWS keeps or clears a\n // previously-set CapacityReservationTarget when only the preference\n // is re-sent is UNPROBED (a live probe needs a billed Capacity\n // Reservation); the kept-partial object passes through unchanged.\n // - AvailabilityZoneDistribution: single sub-field — no partial shape\n // exists.\n const healthCheckTypeInput = clearOnUpdateRemoval(\n properties['HealthCheckType'] as string | undefined,\n previousProperties['HealthCheckType'] as string | undefined,\n // SDK doc: \"EC2 is the default health check and cannot be disabled.\n // ... Only specify EC2 if you must clear a value that was previously\n // set.\"\n 'EC2'\n );\n const healthCheckGracePeriodInput = clearOnUpdateRemoval(\n properties['HealthCheckGracePeriod'] != null\n ? Number(properties['HealthCheckGracePeriod'])\n : undefined,\n previousProperties['HealthCheckGracePeriod'] != null\n ? Number(previousProperties['HealthCheckGracePeriod'])\n : undefined,\n // CFn default: 0 seconds.\n 0\n );\n // CFn's template key is `Cooldown`; cdkd also accepts the SDK-side\n // spelling `DefaultCooldown`. Treat the two keys as ONE logical field on\n // both sides so switching spellings is never misread as a removal.\n const cooldownRaw = properties['Cooldown'] ?? properties['DefaultCooldown'];\n const prevCooldownRaw =\n previousProperties['Cooldown'] ?? previousProperties['DefaultCooldown'];\n const defaultCooldownInput = clearOnUpdateRemoval(\n cooldownRaw != null ? Number(cooldownRaw) : undefined,\n prevCooldownRaw != null ? Number(prevCooldownRaw) : undefined,\n // CFn default: 300 seconds.\n 300\n );\n const terminationPoliciesInput = clearOnUpdateRemoval(\n properties['TerminationPolicies'] as string[] | undefined,\n previousProperties['TerminationPolicies'] as string[] | undefined,\n // CFn/API default termination policy.\n ['Default']\n );\n const newInstancesProtectedInput = clearOnUpdateRemoval(\n properties['NewInstancesProtectedFromScaleIn'] as boolean | undefined,\n previousProperties['NewInstancesProtectedFromScaleIn'] as boolean | undefined,\n false\n );\n const capacityRebalanceInput = clearOnUpdateRemoval(\n properties['CapacityRebalance'] as boolean | undefined,\n previousProperties['CapacityRebalance'] as boolean | undefined,\n false\n );\n const maxInstanceLifetimeInput = clearOnUpdateRemoval(\n properties['MaxInstanceLifetime'] != null\n ? Number(properties['MaxInstanceLifetime'])\n : undefined,\n previousProperties['MaxInstanceLifetime'] != null\n ? Number(previousProperties['MaxInstanceLifetime'])\n : undefined,\n // SDK doc: \"To clear a previously set value, specify a new value of 0.\"\n 0\n );\n const desiredCapacityTypeInput = clearOnUpdateRemoval(\n properties['DesiredCapacityType'] as string | undefined,\n previousProperties['DesiredCapacityType'] as string | undefined,\n // SDK doc: \"By default, Amazon EC2 Auto Scaling specifies units\".\n 'units'\n );\n const defaultInstanceWarmupInput = clearOnUpdateRemoval(\n properties['DefaultInstanceWarmup'] != null\n ? Number(properties['DefaultInstanceWarmup'])\n : undefined,\n previousProperties['DefaultInstanceWarmup'] != null\n ? Number(previousProperties['DefaultInstanceWarmup'])\n : undefined,\n // SDK doc: \"To remove a value that you previously set, include the\n // property but specify -1 for the value.\"\n -1\n );\n const instanceMaintenancePolicyInput = clearOnUpdateRemoval(\n properties['InstanceMaintenancePolicy'] as InstanceMaintenancePolicy | undefined,\n previousProperties['InstanceMaintenancePolicy'] as InstanceMaintenancePolicy | undefined,\n // SDK doc (both sub-fields): \"To clear a previously set value,\n // specify a value of -1.\"\n { MinHealthyPercentage: -1, MaxHealthyPercentage: -1 }\n );\n const capacityReservationSpecInput = clearOnUpdateRemoval(\n properties['CapacityReservationSpecification'] as\n | CapacityReservationSpecification\n | undefined,\n previousProperties['CapacityReservationSpecification'] as\n | CapacityReservationSpecification\n | undefined,\n // SDK doc: \"default - Auto Scaling uses the Capacity Reservation\n // preference from your launch template or an open Capacity\n // Reservation.\" — the behavior of a group that never set the field.\n { CapacityReservationPreference: 'default' }\n );\n const availabilityZoneDistributionInput = clearOnUpdateRemoval(\n properties['AvailabilityZoneDistribution'] as AvailabilityZoneDistribution | undefined,\n previousProperties['AvailabilityZoneDistribution'] as\n | AvailabilityZoneDistribution\n | undefined,\n // SDK doc: \"The default is balanced-best-effort.\"\n { CapacityDistributionStrategy: 'balanced-best-effort' }\n );\n const deletionProtectionInput = clearOnUpdateRemoval(\n properties['DeletionProtection'] as DeletionProtection | undefined,\n previousProperties['DeletionProtection'] as DeletionProtection | undefined,\n // SDK doc: \"Default: none\" — also the flip-off value delete() uses.\n 'none'\n );\n\n await this.getClient().send(\n new UpdateAutoScalingGroupCommand({\n AutoScalingGroupName: physicalId,\n ...(properties['MinSize'] != null && { MinSize: Number(properties['MinSize']) }),\n ...(properties['MaxSize'] != null && { MaxSize: Number(properties['MaxSize']) }),\n ...(properties['DesiredCapacity'] != null && {\n DesiredCapacity: Number(properties['DesiredCapacity']),\n }),\n ...(launchTemplate && { LaunchTemplate: launchTemplate }),\n ...(properties['MixedInstancesPolicy'] !== undefined && {\n MixedInstancesPolicy: properties['MixedInstancesPolicy'] as never,\n }),\n ...(vpcZoneIdentifier !== undefined && { VPCZoneIdentifier: vpcZoneIdentifier }),\n ...(properties['AvailabilityZones'] !== undefined && {\n AvailabilityZones: properties['AvailabilityZones'] as string[],\n }),\n ...(healthCheckTypeInput !== undefined && {\n HealthCheckType: healthCheckTypeInput,\n }),\n ...(healthCheckGracePeriodInput !== undefined && {\n HealthCheckGracePeriod: healthCheckGracePeriodInput,\n }),\n ...(defaultCooldownInput !== undefined && {\n DefaultCooldown: defaultCooldownInput,\n }),\n ...(terminationPoliciesInput !== undefined && {\n TerminationPolicies: terminationPoliciesInput,\n }),\n ...(newInstancesProtectedInput !== undefined && {\n NewInstancesProtectedFromScaleIn: newInstancesProtectedInput,\n }),\n ...(capacityRebalanceInput !== undefined && {\n CapacityRebalance: capacityRebalanceInput,\n }),\n ...(properties['ServiceLinkedRoleARN'] !== undefined && {\n ServiceLinkedRoleARN: properties['ServiceLinkedRoleARN'] as string,\n }),\n ...(maxInstanceLifetimeInput !== undefined && {\n MaxInstanceLifetime: maxInstanceLifetimeInput,\n }),\n ...(properties['Context'] !== undefined && {\n Context: properties['Context'] as string,\n }),\n ...(desiredCapacityTypeInput !== undefined && {\n DesiredCapacityType: desiredCapacityTypeInput,\n }),\n ...(defaultInstanceWarmupInput !== undefined && {\n DefaultInstanceWarmup: defaultInstanceWarmupInput,\n }),\n ...(availabilityZoneDistributionInput !== undefined && {\n AvailabilityZoneDistribution: availabilityZoneDistributionInput,\n }),\n // Removal reset DEFERRED (no SDK-documented default for the\n // sub-fields) — see the comment block above.\n ...(properties['AvailabilityZoneImpairmentPolicy'] !== undefined && {\n AvailabilityZoneImpairmentPolicy: properties[\n 'AvailabilityZoneImpairmentPolicy'\n ] as never,\n }),\n ...(properties['SkipZonalShiftValidation'] !== undefined && {\n SkipZonalShiftValidation: properties['SkipZonalShiftValidation'] as boolean,\n }),\n ...(capacityReservationSpecInput !== undefined && {\n CapacityReservationSpecification: capacityReservationSpecInput,\n }),\n ...(instanceMaintenancePolicyInput !== undefined && {\n InstanceMaintenancePolicy: instanceMaintenancePolicyInput,\n }),\n ...(deletionProtectionInput !== undefined && {\n DeletionProtection: deletionProtectionInput,\n }),\n })\n );\n\n this.logger.debug(`Successfully updated AutoScalingGroup ${logicalId}`);\n\n const arn = await this.fetchArn(physicalId);\n const attributes: Record<string, unknown> = {};\n if (arn) attributes['Arn'] = arn;\n if (launchTemplate?.LaunchTemplateId) {\n attributes['LaunchTemplateID'] = launchTemplate.LaunchTemplateId;\n }\n return { physicalId, wasReplaced: false, attributes };\n } catch (error) {\n if (error instanceof ResourceUpdateNotSupportedError) throw error;\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to update AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n async delete(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n _properties?: Record<string, unknown>,\n context?: DeleteContext\n ): Promise<void> {\n this.logger.debug(`Deleting AutoScalingGroup ${logicalId}: ${physicalId}`);\n\n // `--remove-protection`: clear DeletionProtection in-place before the\n // actual delete, then set ForceDelete=true so AWS terminates running\n // instances as part of the delete (matches the \"I know what I'm doing\"\n // intent of the flag). Without `removeProtection`, ForceDelete stays\n // false and AWS rejects the delete on a group with running instances\n // or DeletionProtection set, surfacing as ProvisioningError. The\n // flip-off is idempotent — AWS accepts UpdateAutoScalingGroup\n // (DeletionProtection: 'none') even when protection is already\n // disabled, so we always issue it under the flag.\n if (context?.removeProtection === true) {\n try {\n await this.getClient().send(\n new UpdateAutoScalingGroupCommand({\n AutoScalingGroupName: physicalId,\n DeletionProtection: 'none' as never,\n })\n );\n this.logger.debug(\n `Disabled DeletionProtection on AutoScalingGroup ${logicalId} before delete`\n );\n } catch (flipError) {\n // Non-fatal: log and proceed. The actual delete below surfaces\n // any real error.\n this.logger.debug(\n `Could not disable DeletionProtection on ${physicalId}: ${describeAwsFailure(flipError).detail}`\n );\n }\n\n // ASG-level DeletionProtection + ForceDelete only governs the GROUP and\n // its scale-in protection. If the group's launch template sets\n // EC2-level termination protection (DisableApiTermination), the\n // ForceDelete below still cannot terminate those instances and they\n // ORPHAN after the group is gone (issue #796). Enumerate the group's\n // current instances and flip each one's DisableApiTermination off first,\n // mirroring the EC2Provider `--remove-protection` path.\n await this.removeInstanceTerminationProtection(physicalId, logicalId);\n }\n\n try {\n await this.getClient().send(\n new DeleteAutoScalingGroupCommand({\n AutoScalingGroupName: physicalId,\n ForceDelete: context?.removeProtection === true,\n })\n );\n\n this.logger.debug(`Successfully initiated deletion of AutoScalingGroup ${logicalId}`);\n\n // Wait for the group to be fully gone. ASG delete is asynchronous —\n // returning immediately would leave dependent EC2 / IAM / SG\n // resources blocked on the lingering group.\n await this.waitForGroupDeleted(physicalId);\n } catch (error) {\n if (this.isNotFoundError(error)) {\n const clientRegion = await this.getClient().config.region();\n assertRegionMatch(\n clientRegion,\n context?.expectedRegion,\n resourceType,\n logicalId,\n physicalId\n );\n this.logger.debug(`AutoScalingGroup ${physicalId} does not exist, skipping deletion`);\n return;\n }\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to delete AutoScalingGroup ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n async getAttribute(\n physicalId: string,\n _resourceType: string,\n attributeName: string\n ): Promise<unknown> {\n const group = await this.describeGroup(physicalId);\n if (!group) {\n throw new ProvisioningError(\n `AutoScalingGroup ${physicalId} not found while resolving attribute ${attributeName}`,\n 'AWS::AutoScaling::AutoScalingGroup',\n physicalId,\n physicalId\n );\n }\n switch (attributeName) {\n case 'Arn':\n case 'AutoScalingGroupARN':\n return group.AutoScalingGroupARN ?? '';\n case 'LaunchConfigurationName':\n return group.LaunchConfigurationName ?? '';\n case 'LaunchTemplateID':\n case 'LaunchTemplateId':\n return group.LaunchTemplate?.LaunchTemplateId ?? '';\n default:\n return '';\n }\n }\n\n /**\n * Read the AWS-current AutoScalingGroup configuration in CFn-property shape.\n *\n * Surfaces the user-controllable subset of `DescribeAutoScalingGroups`,\n * with always-emit placeholders on user-controllable top-level keys per\n * the cdkd PR #145 always-emit convention so that v3 `observedProperties`\n * baseline catches console-side ADDs to fields a clean deploy did not\n * template (e.g. a console-set `DeletionProtection: 'prevent-force-deletion'`\n * on a group originally created without it).\n *\n * Sub-shapes (LifecycleHookSpecificationList / TrafficSources /\n * NotificationConfigurations) are surfaced via three parallel Describe\n * calls fired alongside the primary `DescribeAutoScalingGroups`. Each is\n * best-effort: a per-call failure (e.g. permissions gap on\n * `autoscaling:DescribeLifecycleHooks`) is logged at debug and the\n * matching key falls back to its always-emit `[]` placeholder rather\n * than aborting the whole drift read.\n *\n * `MetricsCollection` is reverse-mapped from `EnabledMetrics` (already\n * present on the primary `DescribeAutoScalingGroups` response, so no\n * extra call is needed).\n *\n * Returns `undefined` when the group is gone.\n */\n async readCurrentState(\n physicalId: string,\n _logicalId: string,\n _resourceType: string\n ): Promise<Record<string, unknown> | undefined> {\n // Fire the four reads in parallel. Sub-shape failures are best-effort\n // so a single permission gap does not break the whole drift read.\n const groupPromise = (async () => {\n try {\n return await this.describeGroup(physicalId);\n } catch (err) {\n if (this.isNotFoundError(err)) return undefined;\n throw err;\n }\n })();\n\n const lifecycleHooksPromise = this.getClient()\n .send(new DescribeLifecycleHooksCommand({ AutoScalingGroupName: physicalId }))\n .then((r) => r.LifecycleHooks ?? [])\n .catch((err) => {\n this.logger.debug(\n `DescribeLifecycleHooks(${physicalId}) failed: ${describeAwsFailure(err).detail}`\n );\n return [];\n });\n\n const trafficSourcesPromise = this.getClient()\n .send(new DescribeTrafficSourcesCommand({ AutoScalingGroupName: physicalId }))\n .then((r) => r.TrafficSources ?? [])\n .catch((err) => {\n this.logger.debug(\n `DescribeTrafficSources(${physicalId}) failed: ${describeAwsFailure(err).detail}`\n );\n return [];\n });\n\n const notificationsPromise = this.getClient()\n .send(new DescribeNotificationConfigurationsCommand({ AutoScalingGroupNames: [physicalId] }))\n .then((r) => r.NotificationConfigurations ?? [])\n .catch((err) => {\n this.logger.debug(\n `DescribeNotificationConfigurations(${physicalId}) failed: ${describeAwsFailure(err).detail}`\n );\n return [];\n });\n\n const [group, lifecycleHooks, trafficSources, notifications] = await Promise.all([\n groupPromise,\n lifecycleHooksPromise,\n trafficSourcesPromise,\n notificationsPromise,\n ]);\n\n if (!group) return undefined;\n\n const result: Record<string, unknown> = {};\n if (group.AutoScalingGroupName !== undefined) {\n result['AutoScalingGroupName'] = group.AutoScalingGroupName;\n }\n if (group.LaunchTemplate) {\n const lt: Record<string, unknown> = {};\n if (group.LaunchTemplate.LaunchTemplateId !== undefined) {\n lt['LaunchTemplateId'] = group.LaunchTemplate.LaunchTemplateId;\n }\n if (group.LaunchTemplate.LaunchTemplateName !== undefined) {\n lt['LaunchTemplateName'] = group.LaunchTemplate.LaunchTemplateName;\n }\n if (group.LaunchTemplate.Version !== undefined) {\n lt['Version'] = group.LaunchTemplate.Version;\n }\n result['LaunchTemplate'] = lt;\n }\n result['MinSize'] = group.MinSize ?? 0;\n result['MaxSize'] = group.MaxSize ?? 0;\n if (group.DesiredCapacity !== undefined) result['DesiredCapacity'] = group.DesiredCapacity;\n // VPCZoneIdentifier round-trips back to the CFn list shape so the\n // comparator sees the same array the template emitted, not the\n // SDK-side comma-joined string.\n if (group.VPCZoneIdentifier !== undefined && group.VPCZoneIdentifier !== '') {\n result['VPCZoneIdentifier'] = group.VPCZoneIdentifier.split(',').map((s) => s.trim());\n } else {\n result['VPCZoneIdentifier'] = [];\n }\n result['AvailabilityZones'] = group.AvailabilityZones ?? [];\n if (group.HealthCheckType !== undefined) result['HealthCheckType'] = group.HealthCheckType;\n if (group.HealthCheckGracePeriod !== undefined) {\n result['HealthCheckGracePeriod'] = group.HealthCheckGracePeriod;\n }\n if (group.DefaultCooldown !== undefined) {\n // CFn template field is `Cooldown`; SDK / Describe response calls it\n // `DefaultCooldown`. Surface under the CFn name so the comparator\n // matches state directly.\n result['Cooldown'] = group.DefaultCooldown;\n }\n result['NewInstancesProtectedFromScaleIn'] = group.NewInstancesProtectedFromScaleIn ?? false;\n result['TerminationPolicies'] = group.TerminationPolicies ?? [];\n result['CapacityRebalance'] = group.CapacityRebalance ?? false;\n if (group.ServiceLinkedRoleARN !== undefined) {\n result['ServiceLinkedRoleARN'] = group.ServiceLinkedRoleARN;\n }\n if (group.MaxInstanceLifetime !== undefined) {\n result['MaxInstanceLifetime'] = group.MaxInstanceLifetime;\n }\n result['LoadBalancerNames'] = group.LoadBalancerNames ?? [];\n result['TargetGroupARNs'] = group.TargetGroupARNs ?? [];\n if (group.Context !== undefined) result['Context'] = group.Context;\n if (group.DesiredCapacityType !== undefined) {\n result['DesiredCapacityType'] = group.DesiredCapacityType;\n }\n if (group.DefaultInstanceWarmup !== undefined) {\n result['DefaultInstanceWarmup'] = group.DefaultInstanceWarmup;\n }\n if (group.MixedInstancesPolicy !== undefined) {\n result['MixedInstancesPolicy'] = group.MixedInstancesPolicy;\n }\n if (group.AvailabilityZoneDistribution !== undefined) {\n result['AvailabilityZoneDistribution'] = group.AvailabilityZoneDistribution;\n }\n if (group.AvailabilityZoneImpairmentPolicy !== undefined) {\n result['AvailabilityZoneImpairmentPolicy'] = group.AvailabilityZoneImpairmentPolicy;\n }\n if (group.CapacityReservationSpecification !== undefined) {\n result['CapacityReservationSpecification'] = group.CapacityReservationSpecification;\n }\n if (group.InstanceMaintenancePolicy !== undefined) {\n result['InstanceMaintenancePolicy'] = group.InstanceMaintenancePolicy;\n }\n if (group.DeletionProtection !== undefined) {\n result['DeletionProtection'] = group.DeletionProtection;\n } else {\n // AWS reports `undefined` when the group has the AWS-side default\n // (`'none'`). Always-emit placeholder so the v3 `observedProperties`\n // baseline catches a console-side flip to `prevent-force-deletion`\n // / `prevent-all-deletion`.\n result['DeletionProtection'] = 'none';\n }\n // Tags: filter aws:* prefix and normalize to CFn shape sorted by Key.\n // ASG returns Tags inside the AutoScalingGroup record (already populated\n // by DescribeAutoScalingGroups — no separate ListTagsForResource call).\n result['Tags'] = normalizeAwsTagsToCfn(group.Tags);\n\n // Sub-shapes — reverse-map AWS responses to CFn template shape and\n // always-emit `[]` placeholders so the v3 `observedProperties` baseline\n // catches console-side ADDs to a previously-empty list.\n result['MetricsCollection'] = mapEnabledMetricsToCfn(group.EnabledMetrics);\n result['LifecycleHookSpecificationList'] = mapLifecycleHooksToCfn(lifecycleHooks);\n // Strip ALL elbv2 / elb entries from TrafficSources — the canonical\n // attachment state for these types lives in TargetGroupARNs /\n // LoadBalancerNames. TrafficSources is meant for attachment types\n // without a dedicated CFn property (VPC Lattice, VPC Endpoint\n // Service). Filtering unconditionally avoids two failure modes\n // surfaced by tests/integration/drift-revert-vpc (PR #547):\n // double-attach/detach on revert, and stale TS entries from AWS's\n // eventual-consistency window after Attach/Detach surfacing as\n // false drift on the next read.\n const dedupedTrafficSources = trafficSources.filter((t) => {\n if (t.Identifier === undefined) return false;\n if (t.Type === 'elbv2' || t.Type === 'elb') return false;\n return true;\n });\n result['TrafficSources'] = mapTrafficSourcesToCfn(dedupedTrafficSources);\n result['NotificationConfigurations'] = mapNotificationsToCfn(notifications);\n\n return result;\n }\n\n // ─── Helpers ──────────────────────────────────────────────────────\n\n private buildLaunchTemplate(\n properties: Record<string, unknown>\n ): LaunchTemplateSpecification | undefined {\n const lt = properties['LaunchTemplate'] as\n | { LaunchTemplateId?: string; LaunchTemplateName?: string; Version?: string | number }\n | undefined;\n if (!lt) return undefined;\n const out: LaunchTemplateSpecification = {};\n // AWS UpdateAutoScalingGroup rejects when both LaunchTemplateId and\n // LaunchTemplateName are present in the same LaunchTemplate object\n // (\"Valid requests must contain either launchTemplateId or\n // LaunchTemplateName\"). DescribeAutoScalingGroups returns both, so\n // a straight readCurrentState → update round-trip on `drift --revert`\n // would hit this. Prefer the ID (canonical, doesn't change on LT\n // rename) and only fall back to Name when ID is absent.\n if (lt.LaunchTemplateId !== undefined) {\n out.LaunchTemplateId = lt.LaunchTemplateId;\n if (lt.LaunchTemplateName !== undefined) {\n // User templated BOTH — AWS would reject the resulting Create /\n // Update otherwise; we silently prefer the ID. Surface the\n // choice in --verbose so a user wondering why their Name didn't\n // take effect has an auditable signal.\n this.logger.debug(\n `buildLaunchTemplate: both LaunchTemplateId (${lt.LaunchTemplateId}) and LaunchTemplateName (${lt.LaunchTemplateName}) templated; dropping Name (#551)`\n );\n }\n } else if (lt.LaunchTemplateName !== undefined) {\n out.LaunchTemplateName = lt.LaunchTemplateName;\n }\n if (lt.Version !== undefined) {\n // Defensive coercion: AWS SDK `LaunchTemplateSpecification.Version`\n // is `string` and AWS rejects non-string forms with `Invalid\n // launch template version: either '$Default', '$Latest', or a\n // numeric version are allowed.`. cdkd's `IntrinsicResolver`\n // resolves `Fn::GetAtt <LaunchTemplate>.LatestVersionNumber`\n // through a per-type lookup; intermediate cases could surface\n // numeric values, so we coerce defensively.\n out.Version = String(lt.Version);\n }\n if (out.LaunchTemplateId === undefined && out.LaunchTemplateName === undefined) {\n return undefined;\n }\n return out;\n }\n\n /**\n * CFn `Tags` is `[{Key, Value, PropagateAtLaunch?}]`. AWS expects each\n * tag to also carry `ResourceId: <groupName>` and `ResourceType:\n * 'auto-scaling-group'`. We tack those on at create time so the SDK\n * input shape matches without forcing the user to template them.\n */\n private buildTags(groupName: string, properties: Record<string, unknown>): ASGTag[] {\n const raw = properties['Tags'] as\n | Array<{ Key?: string; Value?: string; PropagateAtLaunch?: boolean }>\n | undefined;\n if (!raw) return [];\n return raw\n .filter((t) => t.Key !== undefined)\n .map((t) => ({\n ResourceId: groupName,\n ResourceType: 'auto-scaling-group',\n Key: t.Key as string,\n Value: t.Value ?? '',\n PropagateAtLaunch: t.PropagateAtLaunch ?? false,\n }));\n }\n\n /**\n * CFn `VPCZoneIdentifier` is a list of subnet ids; the AWS SDK input\n * field is a comma-joined string.\n */\n private joinVpcZoneIdentifier(value: unknown): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (Array.isArray(value)) {\n const cleaned = value.map((v) => String(v).trim()).filter((v) => v.length > 0);\n if (cleaned.length === 0) return undefined;\n return cleaned.join(',');\n }\n if (typeof value === 'string') return value;\n return undefined;\n }\n\n private async describeGroup(groupName: string) {\n const response = await this.getClient().send(\n new DescribeAutoScalingGroupsCommand({\n AutoScalingGroupNames: [groupName],\n })\n );\n return response.AutoScalingGroups?.[0];\n }\n\n /**\n * Flip EC2-level termination protection (`DisableApiTermination`) off on\n * every instance currently launched by the group, so the subsequent\n * `DeleteAutoScalingGroup(ForceDelete: true)` can actually terminate them\n * instead of orphaning the protected instances (issue #796). Best-effort:\n * a Describe failure or a per-instance flip failure is logged at debug and\n * does not block the delete (the modify WRITE lags the terminate READ, so\n * the shared helper swallows propagation errors the same way the EC2 path\n * does — the orphan, if any, surfaces as a leftover instance the caller\n * can clean up rather than a hard delete failure).\n */\n private async removeInstanceTerminationProtection(\n groupName: string,\n logicalId: string\n ): Promise<void> {\n let instanceIds: string[];\n try {\n const group = await this.describeGroup(groupName);\n instanceIds = (group?.Instances ?? [])\n .map((i) => i.InstanceId)\n .filter((id): id is string => typeof id === 'string' && id.length > 0);\n } catch (describeError) {\n this.logger.debug(\n `Could not enumerate instances of AutoScalingGroup ${logicalId} for termination-protection removal: ${describeAwsFailure(describeError).detail}`\n );\n return;\n }\n\n if (instanceIds.length === 0) return;\n\n this.logger.debug(\n `Disabling EC2 termination protection on ${instanceIds.length} instance(s) of AutoScalingGroup ${logicalId} before force delete`\n );\n for (const instanceId of instanceIds) {\n await disableInstanceApiTermination(this.getEc2Client(), instanceId, this.logger);\n }\n }\n\n private async fetchArn(groupName: string): Promise<string | undefined> {\n try {\n const group = await this.describeGroup(groupName);\n return group?.AutoScalingGroupARN;\n } catch (err) {\n this.logger.debug(\n `DescribeAutoScalingGroups(${groupName}) failed: ${describeAwsFailure(err).detail}`\n );\n return undefined;\n }\n }\n\n private isNotFoundError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const name = (error as { name?: string }).name ?? '';\n const message = error.message.toLowerCase();\n // ASG returns ValidationError with message \"AutoScalingGroup name not\n // found\" rather than a typed NotFound exception; cover both shapes.\n return (\n name === 'ValidationError' &&\n (message.includes('autoscalinggroup name not found') ||\n message.includes('not found') ||\n message.includes('does not exist'))\n );\n }\n\n private async waitForGroupDeleted(groupName: string, maxWaitMs = 900_000): Promise<void> {\n const startTime = Date.now();\n let delay = 5_000;\n\n while (Date.now() - startTime < maxWaitMs) {\n try {\n const group = await this.describeGroup(groupName);\n if (!group) return;\n } catch (error) {\n if (this.isNotFoundError(error)) return;\n throw error;\n }\n\n await this.sleep(delay);\n delay = Math.min(delay * 2, 10_000);\n }\n\n throw new Error(\n `Timed out waiting for AutoScalingGroup ${groupName} to be deleted (15 minute cap)`\n );\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n // ─── Sub-shape diff helpers ───────────────────────────────────────\n // Each helper is a no-op when before/after JSON is identical (the cheap\n // structural-equality check happens first; we only build SDK calls for\n // genuine diffs). Identity is positional within the array per CFn shape:\n // `MetricsCollection` keyed on `Granularity`, `LifecycleHookSpecification\n // List` on `LifecycleHookName`, `TrafficSources` on `Identifier`,\n // `NotificationConfigurations` on `TopicARN`.\n\n /**\n * Diff and apply changes to the ASG's `Tags` property via the\n * `CreateOrUpdateTags` / `DeleteTags` AWS APIs (#475). CFn Tags shape is\n * `[{Key, Value, PropagateAtLaunch}]`; AWS Tag input adds `ResourceId`\n * (= the ASG name) and `ResourceType: 'auto-scaling-group'`.\n *\n * Diff semantics:\n * - Removed keys → `DeleteTags`.\n * - Added keys → `CreateOrUpdateTags`.\n * - Modified value or `PropagateAtLaunch` flag → `CreateOrUpdateTags`\n * (the AWS API upserts by `(ResourceId, ResourceType, Key)` tuple, so\n * a single upsert call replaces the old value).\n *\n * No-op when before/after JSON is identical.\n */\n private async applyTagsDiff(physicalId: string, next: unknown, prev: unknown): Promise<void> {\n if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;\n type CfnTag = { Key?: string; Value?: string; PropagateAtLaunch?: boolean };\n const nextEntries = (Array.isArray(next) ? next : []) as CfnTag[];\n const prevEntries = (Array.isArray(prev) ? prev : []) as CfnTag[];\n const nextByKey = new Map<string, CfnTag>();\n for (const t of nextEntries) {\n if (t.Key) nextByKey.set(t.Key, t);\n }\n const prevByKey = new Map<string, CfnTag>();\n for (const t of prevEntries) {\n if (t.Key) prevByKey.set(t.Key, t);\n }\n // Delete keys removed from `next`.\n const toDelete: CfnTag[] = [];\n for (const [key, tag] of prevByKey) {\n if (!nextByKey.has(key)) toDelete.push(tag);\n }\n if (toDelete.length > 0) {\n await this.getClient().send(\n new DeleteTagsCommand({\n // DeleteTags is keyed only by (ResourceId, ResourceType, Key).\n // Intentionally omit `Value` / `PropagateAtLaunch`: AWS treats\n // those as additional match constraints, so passing the\n // cdkd-recorded values would silently no-op when a console-side\n // edit drifted them between deploys. cdkd owns the tag, so\n // delete-by-key matches the \"we own the resource\" intent.\n Tags: toDelete.map((t) => ({\n ResourceId: physicalId,\n ResourceType: 'auto-scaling-group',\n Key: t.Key as string,\n })),\n })\n );\n }\n // Upsert keys whose value / propagate-flag differs.\n const toUpsert: CfnTag[] = [];\n for (const [key, tag] of nextByKey) {\n const before = prevByKey.get(key);\n if (JSON.stringify(before) === JSON.stringify(tag)) continue;\n toUpsert.push(tag);\n }\n if (toUpsert.length > 0) {\n await this.getClient().send(\n new CreateOrUpdateTagsCommand({\n Tags: toUpsert.map((t) => ({\n ResourceId: physicalId,\n ResourceType: 'auto-scaling-group',\n Key: t.Key as string,\n ...(t.Value !== undefined && { Value: t.Value }),\n ...(t.PropagateAtLaunch !== undefined && {\n PropagateAtLaunch: t.PropagateAtLaunch,\n }),\n })),\n })\n );\n }\n }\n\n /**\n * Diff `LoadBalancerNames` (Classic Load Balancers) and issue\n * `AttachLoadBalancers` / `DetachLoadBalancers` for the delta (#476).\n * Names are opaque strings; AWS allows N attached LBs per ASG so this\n * helper batches every add into one Attach call and every remove into\n * one Detach call. No-op when before/after JSON is identical.\n */\n private async applyLoadBalancerNamesDiff(\n physicalId: string,\n next: unknown,\n prev: unknown\n ): Promise<void> {\n if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;\n const nextNames = (Array.isArray(next) ? next : []).filter(\n (n): n is string => typeof n === 'string'\n );\n const prevNames = (Array.isArray(prev) ? prev : []).filter(\n (n): n is string => typeof n === 'string'\n );\n const nextSet = new Set(nextNames);\n const prevSet = new Set(prevNames);\n const toAttach = nextNames.filter((n) => !prevSet.has(n));\n const toDetach = prevNames.filter((n) => !nextSet.has(n));\n if (toDetach.length > 0) {\n await this.getClient().send(\n new DetachLoadBalancersCommand({\n AutoScalingGroupName: physicalId,\n LoadBalancerNames: toDetach,\n })\n );\n }\n if (toAttach.length > 0) {\n await this.getClient().send(\n new AttachLoadBalancersCommand({\n AutoScalingGroupName: physicalId,\n LoadBalancerNames: toAttach,\n })\n );\n }\n }\n\n /**\n * Diff `TargetGroupARNs` (ALB / NLB target groups) and issue\n * `AttachLoadBalancerTargetGroups` /\n * `DetachLoadBalancerTargetGroups` for the delta (#476). Target-group\n * ARNs are opaque strings; same per-call batching pattern as\n * `applyLoadBalancerNamesDiff`. No-op when before/after JSON is\n * identical.\n */\n private async applyTargetGroupArnsDiff(\n physicalId: string,\n next: unknown,\n prev: unknown,\n maskSecrets?: SecretMasker\n ): Promise<void> {\n if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;\n const nextArns = (Array.isArray(next) ? next : []).filter(\n (a): a is string => typeof a === 'string'\n );\n const prevArns = (Array.isArray(prev) ? prev : []).filter(\n (a): a is string => typeof a === 'string'\n );\n const nextSet = new Set(nextArns);\n const prevSet = new Set(prevArns);\n const toAttach = nextArns.filter((a) => !prevSet.has(a));\n const toDetach = prevArns.filter((a) => !nextSet.has(a));\n if (toDetach.length > 0) {\n await this.getClient().send(\n new DetachLoadBalancerTargetGroupsCommand({\n AutoScalingGroupName: physicalId,\n TargetGroupARNs: toDetach,\n })\n );\n }\n if (toAttach.length > 0) {\n await this.getClient().send(\n new AttachLoadBalancerTargetGroupsCommand({\n AutoScalingGroupName: physicalId,\n TargetGroupARNs: toAttach,\n })\n );\n }\n // AttachLoadBalancerTargetGroups is async — the target group starts in\n // 'Adding' state and only becomes visible in\n // DescribeAutoScalingGroups.TargetGroupARNs after AWS internal\n // propagation. A subsequent `cdkd drift` read right after the call\n // returns can otherwise see a stale snapshot and report drift\n // against the AWS-side empty list (surfaced by tests/integration/\n // drift-revert-vpc's step-6 \"drift again\" check). Bounded poll to\n // confirm the post-state matches the intent before returning so the\n // caller's next read is consistent.\n if (toDetach.length > 0 || toAttach.length > 0) {\n await this.waitForTargetGroupArnsConvergence(physicalId, new Set(nextArns), maskSecrets);\n }\n }\n\n private static readonly TG_CONVERGENCE_TIMEOUT_MS = 30_000;\n private static readonly TG_CONVERGENCE_POLL_INTERVAL_MS = 1_000;\n\n /**\n * `maskSecrets` is the caller's secret masker (issue #1932 item 3, adopted\n * here by issue #1997), threaded from `UpdateContext` via\n * `applyTargetGroupArnsDiff`. `expected` is built from the RESOLVED\n * `TargetGroupARNs` bag, so a `{{resolve:secretsmanager:...}}` scalar in it is\n * already plaintext by the time this method names it. It defaults to IDENTITY\n * so every existing caller and unit test keeps working unchanged.\n */\n private async waitForTargetGroupArnsConvergence(\n physicalId: string,\n expected: Set<string>,\n maskSecrets: SecretMasker = (text) => text\n ): Promise<void> {\n // ONE masked sink per level for every line in this method (issue #1997),\n // rather than a `maskSecrets(...)` at each call: a line added later is\n // masked by construction instead of by the author remembering. Each is the\n // OUTER of two layers — a finished message is always longer than the value\n // inside it, so it can only reach `maskSecretsInText`'s SUBSTRING arm, which\n // ignores needles below `MIN_NEEDLE_LENGTH` (4); the per-ARN mask below is\n // the inner layer, reaching the WHOLE-VALUE arm at any length.\n const warn = (message: string): void => this.logger.warn(maskSecrets(message));\n const debug = (message: string): void => this.logger.debug(maskSecrets(message));\n const deadlineMs = Date.now() + ASGProvider.TG_CONVERGENCE_TIMEOUT_MS;\n let lastObserved: Set<string> = new Set();\n while (Date.now() < deadlineMs) {\n let resp: DescribeAutoScalingGroupsCommandOutput | undefined;\n try {\n resp = await this.getClient().send(\n new DescribeAutoScalingGroupsCommand({ AutoScalingGroupNames: [physicalId] })\n );\n } catch (err) {\n // Transient throttle / network blip during the 30s window must\n // not throw out of applyTargetGroupArnsDiff — the Attach/Detach\n // already succeeded, and propagating would fail the whole\n // update path. Log + retry; the loop will fall through to the\n // timeout-warn path if the API is genuinely down.\n debug(\n `applyTargetGroupArnsDiff convergence poll: transient error, retrying — ${\n describeAwsFailure(err).detail\n }`\n );\n await new Promise((r) => setTimeout(r, ASGProvider.TG_CONVERGENCE_POLL_INTERVAL_MS));\n continue;\n }\n lastObserved = new Set(resp.AutoScalingGroups?.[0]?.TargetGroupARNs ?? []);\n if (lastObserved.size === expected.size && [...expected].every((a) => lastObserved.has(a))) {\n return;\n }\n await new Promise((r) => setTimeout(r, ASGProvider.TG_CONVERGENCE_POLL_INTERVAL_MS));\n }\n // Timeout — surface as a warning rather than failure so the caller\n // still sees the SDK-side success; drift can re-report if the\n // propagation is still stuck. Includes observed vs expected so\n // post-mortem doesn't need a re-deploy.\n // Sort both sides before stringify for visual symmetry — expected\n // comes from the caller's insertion order, observed from AWS-side\n // order; eyeballing the diff in logs is easier when both are sorted.\n //\n // Each ARN is masked INDIVIDUALLY before being stringified (issue #1997):\n // `JSON.stringify` escapes `\"` / `\\` / newlines, so a secret carrying any of\n // them no longer OCCURS in the finished line and the outer sink alone would\n // miss it. The observed side is masked too — it comes back from AWS, but a\n // resolved secret sent by an earlier deploy is exactly what AWS echoes.\n const expectedSorted = [...expected].sort().map((a) => maskSecrets(a));\n const observedSorted = [...lastObserved].sort().map((a) => maskSecrets(a));\n warn(\n `applyTargetGroupArnsDiff: TG set did not converge within ${ASGProvider.TG_CONVERGENCE_TIMEOUT_MS}ms for ASG ${physicalId}. expected=${JSON.stringify(expectedSorted)} observed=${JSON.stringify(observedSorted)}`\n );\n }\n\n private async applyMetricsCollectionDiff(\n physicalId: string,\n next: unknown,\n prev: unknown\n ): Promise<void> {\n if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;\n const nextEntries = (Array.isArray(next) ? next : []) as Array<{\n Granularity?: string;\n Metrics?: string[];\n }>;\n const prevEntries = (Array.isArray(prev) ? prev : []) as Array<{\n Granularity?: string;\n Metrics?: string[];\n }>;\n const prevByGranularity = new Map<string, string[] | undefined>();\n for (const e of prevEntries) {\n if (e.Granularity) prevByGranularity.set(e.Granularity, e.Metrics);\n }\n const nextByGranularity = new Map<string, string[] | undefined>();\n for (const e of nextEntries) {\n if (e.Granularity) nextByGranularity.set(e.Granularity, e.Metrics);\n }\n // Disable removed granularities first, then issue Enable for the\n // intended state of every Granularity in `next`. AWS treats Enable as\n // additive within a Granularity, so a remove-then-add pattern works\n // even when the Metrics list shrinks.\n for (const [granularity, metrics] of prevByGranularity) {\n if (!nextByGranularity.has(granularity)) {\n await this.getClient().send(\n new DisableMetricsCollectionCommand({\n AutoScalingGroupName: physicalId,\n ...(metrics && metrics.length > 0 ? { Metrics: metrics } : {}),\n })\n );\n }\n }\n for (const [granularity, metrics] of nextByGranularity) {\n const before = prevByGranularity.get(granularity);\n if (JSON.stringify(before ?? null) === JSON.stringify(metrics ?? null)) continue;\n // If the Metrics list shrunk, disable the removed metrics first\n // (AWS Enable is additive). When `metrics` is undefined or empty,\n // AWS treats that as \"all metrics\" — disable any prior subset\n // before re-enabling the full set.\n if (before && before.length > 0) {\n const removed = metrics ? before.filter((m) => !metrics.includes(m)) : [];\n if (removed.length > 0) {\n await this.getClient().send(\n new DisableMetricsCollectionCommand({\n AutoScalingGroupName: physicalId,\n Metrics: removed,\n })\n );\n }\n }\n await this.getClient().send(\n new EnableMetricsCollectionCommand({\n AutoScalingGroupName: physicalId,\n Granularity: granularity,\n ...(metrics && metrics.length > 0 ? { Metrics: metrics } : {}),\n })\n );\n }\n }\n\n private async applyLifecycleHooksDiff(\n physicalId: string,\n next: unknown,\n prev: unknown\n ): Promise<void> {\n if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;\n const nextEntries = (Array.isArray(next) ? next : []) as Array<{\n LifecycleHookName?: string;\n LifecycleTransition?: string;\n RoleARN?: string;\n NotificationTargetARN?: string;\n NotificationMetadata?: string;\n HeartbeatTimeout?: number;\n DefaultResult?: string;\n }>;\n const prevEntries = (Array.isArray(prev) ? prev : []) as Array<{\n LifecycleHookName?: string;\n }>;\n const nextNames = new Set(\n nextEntries.map((e) => e.LifecycleHookName).filter((n): n is string => !!n)\n );\n // Delete hooks no longer in `next`.\n for (const e of prevEntries) {\n if (e.LifecycleHookName && !nextNames.has(e.LifecycleHookName)) {\n await this.getClient().send(\n new DeleteLifecycleHookCommand({\n AutoScalingGroupName: physicalId,\n LifecycleHookName: e.LifecycleHookName,\n })\n );\n }\n }\n // PutLifecycleHook is upsert — issue for every hook in `next` whose\n // shape differs from the matching `prev` entry.\n const prevByName = new Map<string, unknown>();\n for (const e of prevEntries) {\n if (e.LifecycleHookName) prevByName.set(e.LifecycleHookName, e);\n }\n for (const e of nextEntries) {\n if (!e.LifecycleHookName) continue;\n const prevHook = prevByName.get(e.LifecycleHookName);\n if (JSON.stringify(prevHook) === JSON.stringify(e)) continue;\n await this.getClient().send(\n new PutLifecycleHookCommand({\n AutoScalingGroupName: physicalId,\n LifecycleHookName: e.LifecycleHookName,\n ...(e.LifecycleTransition !== undefined && {\n LifecycleTransition: e.LifecycleTransition,\n }),\n ...(e.RoleARN !== undefined && { RoleARN: e.RoleARN }),\n ...(e.NotificationTargetARN !== undefined && {\n NotificationTargetARN: e.NotificationTargetARN,\n }),\n ...(e.NotificationMetadata !== undefined && {\n NotificationMetadata: e.NotificationMetadata,\n }),\n ...(e.HeartbeatTimeout !== undefined && { HeartbeatTimeout: e.HeartbeatTimeout }),\n ...(e.DefaultResult !== undefined && { DefaultResult: e.DefaultResult }),\n })\n );\n }\n }\n\n private async applyTrafficSourcesDiff(\n physicalId: string,\n next: unknown,\n prev: unknown\n ): Promise<void> {\n if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;\n const nextEntries = (Array.isArray(next) ? next : []) as Array<{\n Identifier?: string;\n Type?: string;\n }>;\n const prevEntries = (Array.isArray(prev) ? prev : []) as Array<{\n Identifier?: string;\n Type?: string;\n }>;\n const nextIds = new Set(nextEntries.map((e) => e.Identifier).filter((i): i is string => !!i));\n const prevIds = new Set(prevEntries.map((e) => e.Identifier).filter((i): i is string => !!i));\n const toDetach = prevEntries.filter((e) => e.Identifier && !nextIds.has(e.Identifier));\n const toAttach = nextEntries.filter((e) => e.Identifier && !prevIds.has(e.Identifier));\n if (toDetach.length > 0) {\n await this.getClient().send(\n new DetachTrafficSourcesCommand({\n AutoScalingGroupName: physicalId,\n TrafficSources: toDetach.map((e) => ({\n Identifier: e.Identifier as string,\n ...(e.Type !== undefined && { Type: e.Type }),\n })),\n })\n );\n }\n if (toAttach.length > 0) {\n await this.getClient().send(\n new AttachTrafficSourcesCommand({\n AutoScalingGroupName: physicalId,\n TrafficSources: toAttach.map((e) => ({\n Identifier: e.Identifier as string,\n ...(e.Type !== undefined && { Type: e.Type }),\n })),\n })\n );\n }\n }\n\n private async applyNotificationConfigurationsDiff(\n physicalId: string,\n next: unknown,\n prev: unknown\n ): Promise<void> {\n if (JSON.stringify(next ?? []) === JSON.stringify(prev ?? [])) return;\n // CFn `NotificationConfigurations` is an array of `{TopicARN,\n // NotificationTypes[]}`; AWS `PutNotificationConfiguration` is keyed\n // by TopicARN — one call per topic. AWS reports each notification\n // type as a separate response entry (one row per `(asgName, topicArn,\n // notificationType)` triple), but cdkd state stores the CFn shape, so\n // both sides of the diff share the per-topic key.\n const nextEntries = (Array.isArray(next) ? next : []) as Array<{\n TopicARN?: string;\n NotificationTypes?: string[];\n }>;\n const prevEntries = (Array.isArray(prev) ? prev : []) as Array<{\n TopicARN?: string;\n NotificationTypes?: string[];\n }>;\n const nextByTopic = new Map<string, string[] | undefined>();\n for (const e of nextEntries) {\n if (e.TopicARN) nextByTopic.set(e.TopicARN, e.NotificationTypes);\n }\n const prevByTopic = new Map<string, string[] | undefined>();\n for (const e of prevEntries) {\n if (e.TopicARN) prevByTopic.set(e.TopicARN, e.NotificationTypes);\n }\n for (const topic of prevByTopic.keys()) {\n if (!nextByTopic.has(topic)) {\n await this.getClient().send(\n new DeleteNotificationConfigurationCommand({\n AutoScalingGroupName: physicalId,\n TopicARN: topic,\n })\n );\n }\n }\n for (const [topic, types] of nextByTopic) {\n const before = prevByTopic.get(topic);\n if (JSON.stringify(before ?? null) === JSON.stringify(types ?? null)) continue;\n await this.getClient().send(\n new PutNotificationConfigurationCommand({\n AutoScalingGroupName: physicalId,\n TopicARN: topic,\n NotificationTypes: types ?? [],\n })\n );\n }\n }\n}\n\n// ─── File-level reverse-mappers (CFn template shape) ────────────────\n\n/**\n * Reverse-map AWS `EnabledMetrics: [{Metric, Granularity}]` (flat list,\n * one row per enabled metric) back to the CFn array shape\n * `[{Granularity, Metrics?[]}]`. Metrics with the same Granularity are\n * grouped together; the resulting Metrics list is sorted alphabetically\n * for stable positional compare in the drift comparator.\n *\n * Always returns a placeholder `[]` per the cdkd PR #145 always-emit\n * convention so a console-side EnableMetricsCollection on a previously-\n * empty group surfaces as drift on the v3 `observedProperties` baseline.\n */\nfunction mapEnabledMetricsToCfn(\n enabledMetrics:\n | Array<{ Metric?: string | undefined; Granularity?: string | undefined }>\n | undefined\n): Array<{ Granularity: string; Metrics?: string[] }> {\n if (!enabledMetrics || enabledMetrics.length === 0) return [];\n const byGranularity = new Map<string, Set<string>>();\n for (const e of enabledMetrics) {\n const g = e.Granularity;\n if (!g) continue;\n let set = byGranularity.get(g);\n if (!set) {\n set = new Set();\n byGranularity.set(g, set);\n }\n if (e.Metric) set.add(e.Metric);\n }\n const result: Array<{ Granularity: string; Metrics?: string[] }> = [];\n // Sort by Granularity for stable positional compare.\n for (const granularity of Array.from(byGranularity.keys()).sort()) {\n const metrics = Array.from(byGranularity.get(granularity) ?? []).sort();\n result.push(\n metrics.length > 0\n ? { Granularity: granularity, Metrics: metrics }\n : { Granularity: granularity }\n );\n }\n return result;\n}\n\n/**\n * Reverse-map AWS `DescribeLifecycleHooks` response to the CFn\n * `LifecycleHookSpecificationList` shape. Each hook is surfaced under the\n * exact CFn property name. AWS-side fields cdkd state never carried\n * (`AutoScalingGroupName` — duplicated on every hook by AWS,\n * `GlobalTimeout` — AWS-derived) are filtered out. Sorted by\n * LifecycleHookName for stable positional compare.\n */\nfunction mapLifecycleHooksToCfn(\n hooks: Array<{\n LifecycleHookName?: string | undefined;\n LifecycleTransition?: string | undefined;\n NotificationTargetARN?: string | undefined;\n RoleARN?: string | undefined;\n NotificationMetadata?: string | undefined;\n HeartbeatTimeout?: number | undefined;\n DefaultResult?: string | undefined;\n }>\n): Array<Record<string, unknown>> {\n if (!hooks || hooks.length === 0) return [];\n const result: Array<Record<string, unknown>> = [];\n for (const h of hooks) {\n if (!h.LifecycleHookName) continue;\n const entry: Record<string, unknown> = { LifecycleHookName: h.LifecycleHookName };\n if (h.LifecycleTransition !== undefined) entry['LifecycleTransition'] = h.LifecycleTransition;\n if (h.RoleARN !== undefined) entry['RoleARN'] = h.RoleARN;\n if (h.NotificationTargetARN !== undefined) {\n entry['NotificationTargetARN'] = h.NotificationTargetARN;\n }\n if (h.NotificationMetadata !== undefined) {\n entry['NotificationMetadata'] = h.NotificationMetadata;\n }\n if (h.HeartbeatTimeout !== undefined) entry['HeartbeatTimeout'] = h.HeartbeatTimeout;\n if (h.DefaultResult !== undefined) entry['DefaultResult'] = h.DefaultResult;\n result.push(entry);\n }\n result.sort((a, b) =>\n String(a['LifecycleHookName']).localeCompare(String(b['LifecycleHookName']))\n );\n return result;\n}\n\n/**\n * Reverse-map AWS `DescribeTrafficSources` response to the CFn\n * `TrafficSources` shape `[{Identifier, Type?}]`. AWS-side runtime fields\n * (`State`, the deprecated `TrafficSource` alias) are filtered out.\n * Sorted by Identifier for stable positional compare.\n */\nfunction mapTrafficSourcesToCfn(\n trafficSources: Array<{ Identifier?: string | undefined; Type?: string | undefined }>\n): Array<Record<string, unknown>> {\n if (!trafficSources || trafficSources.length === 0) return [];\n const result: Array<Record<string, unknown>> = [];\n for (const t of trafficSources) {\n if (!t.Identifier) continue;\n const entry: Record<string, unknown> = { Identifier: t.Identifier };\n if (t.Type !== undefined) entry['Type'] = t.Type;\n result.push(entry);\n }\n result.sort((a, b) => String(a['Identifier']).localeCompare(String(b['Identifier'])));\n return result;\n}\n\n/**\n * Reverse-map AWS `DescribeNotificationConfigurations` (a flat list, one\n * row per `(topicArn, notificationType)`) into the CFn shape\n * `[{TopicARN, NotificationTypes[]}]`. NotificationTypes are grouped per\n * TopicARN and sorted alphabetically for stable positional compare.\n */\nfunction mapNotificationsToCfn(\n configurations: Array<{ TopicARN?: string | undefined; NotificationType?: string | undefined }>\n): Array<Record<string, unknown>> {\n if (!configurations || configurations.length === 0) return [];\n const byTopic = new Map<string, Set<string>>();\n for (const c of configurations) {\n if (!c.TopicARN) continue;\n let set = byTopic.get(c.TopicARN);\n if (!set) {\n set = new Set();\n byTopic.set(c.TopicARN, set);\n }\n if (c.NotificationType) set.add(c.NotificationType);\n }\n const result: Array<Record<string, unknown>> = [];\n for (const topic of Array.from(byTopic.keys()).sort()) {\n const types = Array.from(byTopic.get(topic) ?? []).sort();\n result.push({ TopicARN: topic, NotificationTypes: types });\n }\n return result;\n}\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
1
2
  import { defaultProvider } from "@aws-sdk/credential-provider-node";
2
3
  import { NodeHttpHandler } from "@smithy/node-http-handler";
3
4
  import { Agent } from "node:http";
@@ -107,6 +108,65 @@ function createInnerAgent(secure, proxyUrl) {
107
108
  return secure ? new HttpsProxyAgent(proxyUrl, INNER_AGENT_OPTIONS) : new HttpProxyAgent(proxyUrl, INNER_AGENT_OPTIONS);
108
109
  }
109
110
 
111
+ //#endregion
112
+ //#region src/utils/stack-aws-scope.ts
113
+ /**
114
+ * The per-stack AWS scope: which clients and which region code running on
115
+ * behalf of ONE stack must use (issue go-to-k/cdkd#1981).
116
+ *
117
+ * `cdkd deploy` runs up to `--stack-concurrency` stacks at once, and stacks may
118
+ * live in different regions. It used to re-point two PROCESS-GLOBAL things per
119
+ * stack: the `getAwsClients()` singleton (`setAwsClients`) and
120
+ * `process.env.AWS_REGION`. Both were restored in each stack's `finally`, so
121
+ * with two stacks in flight a provider call made after any `await` could read
122
+ * the OTHER stack's clients or region and create a resource in the wrong
123
+ * region.
124
+ *
125
+ * An `AsyncLocalStorage` scope has neither problem: it is bound to the async
126
+ * call chain that entered it, so every continuation of stack A sees stack A's
127
+ * scope no matter what stack B does in between. The consumers read it through
128
+ * three doors, so callers do not change:
129
+ *
130
+ * - `getAwsClients()` returns the scope's clients before the global;
131
+ * - `awsClientDefaults()` adds the scope's `region`, which reaches every SDK
132
+ * client a provider builds for itself (that helper is spread FIRST at every
133
+ * construction site, so a site's own explicit `region` still wins);
134
+ * - {@link ambientRegion} replaces direct `process.env.AWS_REGION` reads.
135
+ *
136
+ * Outside a scope every door behaves exactly as before (the global singleton,
137
+ * no injected region, the environment variable), so commands that never enter
138
+ * one are unchanged.
139
+ *
140
+ * A LEAF apart from a type-only import: `aws-clients.ts` and
141
+ * `aws-client-defaults.ts` both import it, and it is inside the closure
142
+ * `scripts/audit-provider-coverage.ts` loads under Node's literal `.ts`
143
+ * resolution (see the note in `aws-clients.ts`).
144
+ */
145
+ const storage = new AsyncLocalStorage();
146
+ /**
147
+ * Run `fn` with `scope` as the AWS scope of everything it (transitively,
148
+ * across `await`s) calls. Scopes nest; the innermost wins.
149
+ */
150
+ function runInStackAwsScope(scope, fn) {
151
+ if (typeof scope.region !== "string" || scope.region === "") throw new Error("runInStackAwsScope requires a non-empty region");
152
+ return storage.run(scope, fn);
153
+ }
154
+ /** The active stack AWS scope, or `undefined` outside one. */
155
+ function currentStackAwsScope() {
156
+ return storage.getStore();
157
+ }
158
+ /**
159
+ * The region code running NOW should treat as its own: the active stack
160
+ * scope's region, else `process.env.AWS_REGION` (the pre-scope behaviour).
161
+ *
162
+ * Use this instead of reading `process.env['AWS_REGION']` in any code a deploy
163
+ * can reach — the environment is process-wide and cannot say which of several
164
+ * concurrently deploying stacks is asking.
165
+ */
166
+ function ambientRegion() {
167
+ return storage.getStore()?.region ?? process.env["AWS_REGION"];
168
+ }
169
+
110
170
  //#endregion
111
171
  //#region src/utils/aws-client-defaults.ts
112
172
  /**
@@ -537,6 +597,14 @@ function callerIdentityForOptOut(options, requestHandler) {
537
597
  * builds on its own and makes this a no-op for every existing user.
538
598
  */
539
599
  function awsClientDefaults(options = {}) {
600
+ const scopeRegion = currentStackAwsScope()?.region;
601
+ const defaults = credentialAndHandlerDefaults(options);
602
+ return scopeRegion === void 0 ? defaults : {
603
+ region: scopeRegion,
604
+ ...defaults
605
+ };
606
+ }
607
+ function credentialAndHandlerDefaults(options) {
540
608
  const proxied = isProxyConfigured();
541
609
  const optedOut = options.ignoreAssumedRole === true;
542
610
  const assumed = optedOut ? void 0 : assumedRoleCredentials;
@@ -570,5 +638,5 @@ function awsClientDefaults(options = {}) {
570
638
  }
571
639
 
572
640
  //#endregion
573
- export { setAssumedRoleCredentials as a, getPreAssumeEnvCredentials as i, awsClientDefaults as n, setPreAssumeEnvCredentials as o, getAssumedRoleCredentials as r, SECOND_ROLE_PUBLISH_REFUSAL as t };
574
- //# sourceMappingURL=aws-client-defaults-BoIxyCir.js.map
641
+ export { setAssumedRoleCredentials as a, currentStackAwsScope as c, getPreAssumeEnvCredentials as i, runInStackAwsScope as l, awsClientDefaults as n, setPreAssumeEnvCredentials as o, getAssumedRoleCredentials as r, ambientRegion as s, SECOND_ROLE_PUBLISH_REFUSAL as t };
642
+ //# sourceMappingURL=aws-client-defaults-jISU4PHo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws-client-defaults-jISU4PHo.js","names":["Agent","HttpsAgent","HttpAgent"],"sources":["../src/utils/proxy-routing-agent.ts","../src/utils/stack-aws-scope.ts","../src/utils/aws-client-defaults.ts"],"sourcesContent":["/**\n * An `http.Agent` that routes each request through the environment's proxy, or\n * direct, according to `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`.\n *\n * WHY A ROUTING AGENT AND NOT A PLAIN PROXY AGENT\n *\n * `NodeHttpHandler` picks its agent by PROTOCOL alone — `httpsAgent` for an\n * `https:` request, `httpAgent` otherwise — and never consults the request's\n * host. `https-proxy-agent` does not read `NO_PROXY` either. So a statically\n * chosen proxy agent cannot express \"this host is exempt\", which is the whole\n * of what `NO_PROXY` means. Deciding per REQUEST is the only place the host is\n * known, and `agent-base` gives us that hook: when `connect()` returns an\n * `http.Agent` rather than a socket, the base class delegates the request to it\n * via `socket.addRequest(req, connectOpts)`.\n *\n * That delegation is also why the INNER agents carry `keepAlive` rather than\n * this one: the sockets pool on whichever agent `connect()` hands back, so a\n * `keepAlive` set only here would never be consulted.\n *\n * WHY THE INNER CACHE IS PER INSTANCE\n *\n * Node's `Agent.prototype.destroy()` walks `[this.freeSockets, this.sockets]`,\n * and the second is the ACTIVE set — it aborts in-flight requests, not just\n * idle sockets. `NodeHttpHandler.destroy()` forwards to `httpAgent` and\n * `httpsAgent` unconditionally, external instances included, and cdkd destroys\n * clients mid-run (`deploy` drops the STS client right after\n * `GetCallerIdentity`). A module-global inner cache would therefore let one\n * client's teardown kill another client's live request. Each `AwsClients`\n * client gets its own routing agent, so the cache is scoped to `this` and\n * {@link destroy} forwards to the inner agents — without that forwarding the\n * tunneled sockets would outlive the client and keep the process alive.\n *\n * The cost of not sharing is duplicate CONNECT + TLS handshakes where two\n * clients talk to the same host. Pooling is per host either way, so sharing\n * could never remove the FIRST handshake to each distinct AWS endpoint, and\n * most runtime traffic goes through the per-service `AwsClients` singleton, so\n * the duplicates are few. This is also what the SDK already does: it builds a\n * `keepAlive` agent per client on the unproxied path.\n */\n\nimport { Agent as HttpAgent } from 'node:http';\nimport type { ClientRequest } from 'node:http';\nimport { Agent as HttpsAgent } from 'node:https';\nimport { Agent, type AgentConnectOpts } from 'agent-base';\nimport { HttpProxyAgent } from 'http-proxy-agent';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { getProxyForUrl } from 'proxy-from-env';\n\n/**\n * Applied to every inner agent.\n *\n * The SDK sets these itself, but ONLY when it is handed a plain option bag —\n * an external `Agent` instance is passed through untouched\n * (`@smithy/node-http-handler`'s `NodeHttpHandler` constructor). Since a\n * routing agent IS such an instance, omitting them would silently drop\n * connection reuse for every proxied run, and a concurrent deploy would\n * renegotiate TLS per request.\n */\nconst INNER_AGENT_OPTIONS = { keepAlive: true, maxSockets: 50 } as const;\n\n/**\n * Build the absolute URL `getProxyForUrl` needs from what `connect()` is given.\n *\n * An IPv6 literal is bracketed, or the `host:port` join produces a string no\n * URL parser accepts (`https://::1:443`) and `NO_PROXY` could never match an\n * IPv6 entry. Unreachable with AWS hostnames; handled because the alternative\n * is a silent wrong answer rather than an error.\n */\nfunction requestUrl(options: AgentConnectOpts): string {\n const secure = options.secureEndpoint;\n const rawHost = options.host ?? 'localhost';\n const host = rawHost.includes(':') && !rawHost.startsWith('[') ? `[${rawHost}]` : rawHost;\n const port = options.port ?? (secure ? 443 : 80);\n return `${secure ? 'https' : 'http'}://${host}:${port}`;\n}\n\nexport class ProxyRoutingAgent extends Agent {\n /**\n * Inner agents by `<scheme>|<proxy url or \"direct\">`.\n *\n * Keyed on the proxy URL and not merely on \"proxied or not\" because\n * `NO_PROXY` is not the only thing that varies per host: `HTTP_PROXY` and\n * `HTTPS_PROXY` may name different proxies, and a single agent bound to one\n * of them would quietly send the other scheme's traffic to the wrong place.\n */\n private readonly innerAgents = new Map<string, HttpAgent>();\n\n connect(_req: ClientRequest, options: AgentConnectOpts): HttpAgent {\n const secure = options.secureEndpoint;\n const proxyUrl = getProxyForUrl(requestUrl(options));\n const key = `${secure ? 'https' : 'http'}|${proxyUrl || 'direct'}`;\n\n const cached = this.innerAgents.get(key);\n if (cached) return cached;\n\n const agent = createInnerAgent(secure, proxyUrl);\n this.innerAgents.set(key, agent);\n return agent;\n }\n\n override destroy(): void {\n for (const agent of this.innerAgents.values()) {\n agent.destroy();\n }\n this.innerAgents.clear();\n super.destroy();\n }\n}\n\nfunction createInnerAgent(secure: boolean, proxyUrl: string): HttpAgent {\n if (!proxyUrl) {\n // No proxy for this host — `NO_PROXY` matched, or no proxy variable is set\n // for this scheme. A plain agent keeps the request byte-identical to the\n // unproxied path.\n return secure ? new HttpsAgent(INNER_AGENT_OPTIONS) : new HttpAgent(INNER_AGENT_OPTIONS);\n }\n // `HttpsProxyAgent` opens a CONNECT tunnel and upgrades it to TLS, so the\n // origin's own certificate is what gets validated; `HttpProxyAgent` rewrites\n // the request line for a plain `http:` origin. Which one applies is decided\n // by the ORIGIN's scheme, not the proxy's — a plain `http://` proxy serves\n // both.\n return secure\n ? new HttpsProxyAgent<string>(proxyUrl, INNER_AGENT_OPTIONS)\n : new HttpProxyAgent<string>(proxyUrl, INNER_AGENT_OPTIONS);\n}\n","/**\n * The per-stack AWS scope: which clients and which region code running on\n * behalf of ONE stack must use (issue go-to-k/cdkd#1981).\n *\n * `cdkd deploy` runs up to `--stack-concurrency` stacks at once, and stacks may\n * live in different regions. It used to re-point two PROCESS-GLOBAL things per\n * stack: the `getAwsClients()` singleton (`setAwsClients`) and\n * `process.env.AWS_REGION`. Both were restored in each stack's `finally`, so\n * with two stacks in flight a provider call made after any `await` could read\n * the OTHER stack's clients or region and create a resource in the wrong\n * region.\n *\n * An `AsyncLocalStorage` scope has neither problem: it is bound to the async\n * call chain that entered it, so every continuation of stack A sees stack A's\n * scope no matter what stack B does in between. The consumers read it through\n * three doors, so callers do not change:\n *\n * - `getAwsClients()` returns the scope's clients before the global;\n * - `awsClientDefaults()` adds the scope's `region`, which reaches every SDK\n * client a provider builds for itself (that helper is spread FIRST at every\n * construction site, so a site's own explicit `region` still wins);\n * - {@link ambientRegion} replaces direct `process.env.AWS_REGION` reads.\n *\n * Outside a scope every door behaves exactly as before (the global singleton,\n * no injected region, the environment variable), so commands that never enter\n * one are unchanged.\n *\n * A LEAF apart from a type-only import: `aws-clients.ts` and\n * `aws-client-defaults.ts` both import it, and it is inside the closure\n * `scripts/audit-provider-coverage.ts` loads under Node's literal `.ts`\n * resolution (see the note in `aws-clients.ts`).\n */\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { AwsClients } from './aws-clients.ts';\n\nexport interface StackAwsScope {\n /** The stack's region. Always a named region: a scope is never region-less. */\n readonly region: string;\n /** Clients bound to {@link region}. */\n readonly clients: AwsClients;\n}\n\nconst storage = new AsyncLocalStorage<StackAwsScope>();\n\n/**\n * Run `fn` with `scope` as the AWS scope of everything it (transitively,\n * across `await`s) calls. Scopes nest; the innermost wins.\n */\nexport function runInStackAwsScope<T>(scope: StackAwsScope, fn: () => T): T {\n if (typeof scope.region !== 'string' || scope.region === '') {\n // A region-less scope would silently fall through to the environment in\n // `awsClientDefaults()` and `ambientRegion()` — the very read this scope\n // exists to replace — so refuse it rather than accept it.\n throw new Error('runInStackAwsScope requires a non-empty region');\n }\n return storage.run(scope, fn);\n}\n\n/** The active stack AWS scope, or `undefined` outside one. */\nexport function currentStackAwsScope(): StackAwsScope | undefined {\n return storage.getStore();\n}\n\n/**\n * The region code running NOW should treat as its own: the active stack\n * scope's region, else `process.env.AWS_REGION` (the pre-scope behaviour).\n *\n * Use this instead of reading `process.env['AWS_REGION']` in any code a deploy\n * can reach — the environment is process-wide and cannot say which of several\n * concurrently deploying stacks is asking.\n */\nexport function ambientRegion(): string | undefined {\n return storage.getStore()?.region ?? process.env['AWS_REGION'];\n}\n","/**\n * Client config every AWS SDK client in cdkd must be built with.\n *\n * WHY THIS EXISTS\n *\n * The AWS SDK for JavaScript v3 does NOT read `HTTPS_PROXY` / `HTTP_PROXY` the\n * way botocore (the AWS CLI) and Go's `net/http` do. Its own guide says a proxy\n * is supplied \"through a third-party HTTP agent\" by whoever constructs the\n * client — so on a machine whose only egress is a corporate proxy, an SDK call\n * dials out directly and fails, typically as\n * `CredentialsProviderError: self-signed certificate in certificate chain`\n * because the direct route is what the network intercepts (issue #2388).\n *\n * Node 24's `NODE_USE_ENV_PROXY=1` is not a way out: it rewires the GLOBAL\n * agent, and every SDK client builds its own.\n *\n * WHY THE CREDENTIAL CHAIN IS INJECTED TOO\n *\n * A client's `requestHandler` does not reach every credential hop:\n *\n * - STS (`role_arn` profiles, web identity) reads `requestHandler` off\n * `parentClientConfig`, so it INHERITS ours. Nothing to do.\n * - `@aws-sdk/credential-provider-sso` builds its portal client from\n * `clientConfig` alone, coalescing only `logger`, `region` and\n * `userAgentAppId` from the caller. `@aws-sdk/token-providers` does the same\n * for the SSO-OIDC refresh. So an SSO profile fails at\n * `resolveSSOCredentials` — BEFORE any service call — unless the chain is\n * constructed here with the handler threaded through `clientConfig`.\n * - IMDS (`@smithy/credential-provider-imds`) and ECS container credentials\n * (`@aws-sdk/credential-provider-http`) call `node:http` / build their own\n * handler, so they bypass a proxy on their own and need no special casing.\n *\n * The chain is therefore built per call. `defaultProvider` MEMOIZES resolved\n * credentials inside the chain instance, so one shared instance would hand a\n * client configured for profile A the credentials of profile B. Per call costs\n * nothing relative to today, since each client already builds its own chain.\n *\n * `clientConfig` carries `requestHandler` and NOTHING else — a `region` there\n * would override the SSO portal's own region — and `profile` is passed\n * ALONGSIDE it, because the built-in chain sees a profile through the client\n * config while an injected chain does not.\n *\n * SPREAD THIS FIRST, SITE-SPECIFIC CONFIG SECOND\n *\n * `new S3Client({ ...awsClientDefaults({ profile }), region, ...(creds && { credentials: creds }) })`\n *\n * A site that supplies its own `credentials` must keep them — `config-loader`'s\n * default-bucket probe reuses the STS client's resolved provider on purpose, so\n * that the bucket it probes is checked as the identity the name was derived\n * from. That provider already carries the handler, because the STS client it\n * came from was built through this helper.\n *\n * WHY THE ASSUMED ROLE IS PUBLISHED FROM HERE\n *\n * `--role-arn` / `CDKD_ROLE_ARN` used to reach the SDK through the `AWS_*`\n * environment variables ALONE (`src/utils/role-arn.ts`). That channel is dead\n * whenever a profile is selected: `@aws-sdk/credential-provider-node` skips the\n * env-var provider entirely once `AWS_PROFILE` is set OR a `profile` is passed\n * on the client config, so `cdkd deploy --profile ci --role-arn <r>` assumed the\n * role, logged it as assumed, and then issued every AWS call as the profile's\n * own principal — in the wrong account, silently (issue\n * [#3130](https://github.com/go-to-k/cdkd/issues/3130)).\n *\n * Clearing `AWS_PROFILE` would not have closed it: cdkd ALSO threads `profile`\n * as an explicit client-config key at every construction site, and an explicit\n * key outranks the environment. So the assumed credentials are published HERE\n * instead, as an explicit `credentials` value — which outranks BOTH, because\n * `resolveAwsSdkSigV4Config` uses `config.credentials` when present and never\n * builds the default chain, leaving `profile` to select non-credential settings\n * only (`role-arn.ts`'s header carries why the profile is left in place).\n *\n * This is the one place that reaches every client without a per-site edit:\n * `scripts/check-aws-client-defaults.ts` requires this helper to be spread\n * FIRST at EVERY `new XxxClient(...)` under `src/**`, with an EMPTY allow-list.\n * The same spread order keeps a site's OWN explicit `credentials` winning — the\n * cross-account `Fn::GetStackOutput` read stays scoped to the producer's role.\n *\n * Clients built OUTSIDE `src/**` (cdk-local's own, the CDK app subprocess) see\n * none of this; they read the `AWS_*` triple `applyRoleArnIfSet` also writes,\n * which the SDK honours for them unless a profile is selected.\n */\n\nimport { defaultProvider } from '@aws-sdk/credential-provider-node';\nimport { NodeHttpHandler } from '@smithy/node-http-handler';\n// The `.ts` spelling below is REQUIRED, not a slip.\n//\n// `scripts/audit-provider-coverage.ts` imports `src/utils/aws-clients.ts`\n// directly and runs under `node`'s native type stripping, which resolves\n// relative specifiers LITERALLY — it does not rewrite `.js` to `.ts` the way\n// TypeScript does at emit time. The constraint is TRANSITIVE: it binds every\n// module reachable from `aws-clients.ts`, this one included, so a `./foo.js`\n// import ANYWHERE in that closure breaks `vp run audit:coverage:check` in CI.\n// `tsconfig.json`'s `rewriteRelativeImportExtensions` is what emits the `.ts`\n// spelling as `.js`, and `tests/unit/utils/aws-clients-region-fold.test.ts`\n// fences the whole closure by resolving it the way `node` would.\nimport { ProxyRoutingAgent } from './proxy-routing-agent.ts';\nimport { currentStackAwsScope } from './stack-aws-scope.ts';\n\nexport interface AwsClientDefaultsOptions {\n /** The profile the calling site was configured with, if any. */\n profile?: string | undefined;\n /**\n * Resolve the caller's OWN credential chain even when `--role-arn` has\n * published an assumed role — for the one site whose question is \"what does\n * THIS profile resolve to\", not \"who is cdkd\".\n *\n * `cdkd local *` forwards the `--profile` identity INTO the emulated Lambda /\n * ECS container (`resolveProfileCredentials`), including into a synthesized\n * shared-credentials file written under that profile's own name. Handing it\n * the assumed role would silently run the user's local function as cdkd's\n * deploy role — usually the MORE privileged of the two, and a different\n * identity than the flag they passed asked for. `--role-arn` governs the\n * calls CDKD issues; `--assume-role` is the flag that governs the emulated\n * function's identity.\n *\n * Do NOT reach for this to \"keep a client on the profile\": every other site\n * is cdkd calling AWS as itself, which is exactly what the role is for.\n *\n * It restores the identity the client would have resolved had no role been\n * published, which takes three different shapes and is NOT simply \"let the\n * SDK's own chain answer\":\n *\n * 1. A profile is selected (a `profile` on the config, or `AWS_PROFILE` —\n * `applyRoleArnIfSet` leaves it in place). `credential-provider-node`\n * skips its env link entirely once a profile is named, so its own chain\n * already resolves the caller and nothing is injected here.\n * 2. No profile, and the caller's environment carried a static triple before\n * the assume. {@link setPreAssumeEnvCredentials} snapshotted it; it is\n * returned as an explicit `credentials` value.\n * 3. No profile and no static triple (SSO / IMDS / a container role). The\n * caller's real chain is injected with its ENV LINK EXCLUDED.\n *\n * **Shape 3's exclusion is what makes the opt-out mean anything, and leaving\n * it out made every site below a no-op.** With no profile selected the\n * chain's FIRST link is `fromEnv()`, reading the very\n * `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` triple\n * `applyRoleArnIfSet` overwrote with the role — so returning `{}` here\n * resolved the ROLE, under the name of opting out of it. Measured on PR\n * #3223: `keys of awsClientDefaults({ignoreAssumedRole:true}) = []` and the\n * client then resolved the role's key. The consequences were live, not\n * theoretical: `ecs-secrets-resolver.ts` read SecureString / Secret\n * PLAINTEXT as the deploy role and injected it into the user's container,\n * and the three `--assume-role` STS hops were taken as the deploy role, so a\n * container could receive a role its caller could not assume.\n */\n ignoreAssumedRole?: boolean | undefined;\n}\n\n/**\n * The temporary credentials an `sts:AssumeRole` produced for `--role-arn`.\n *\n * Structurally identical to `AwsCredentials` in `src/utils/role-arn.ts` (which\n * is where the value is produced) and to the SDK's `AwsCredentialIdentity`.\n * Spelled here rather than imported from either because this module sits in the\n * closure `scripts/audit-provider-coverage.ts` resolves LITERALLY (see the\n * `.ts` import note below) and must not grow a relative `.js` import, and\n * because naming the smithy type would cost a new direct dependency.\n */\nexport interface AssumedRoleCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken: string;\n /**\n * When the STS session stops working. CARRIED, NOT ACTED ON — nothing in this\n * module or in `awsClientDefaults`'s consumers reads it, and there is no\n * refresh: the bag published here is STATIC, so a client built from it keeps\n * presenting the same credentials until the process ends.\n *\n * The consequence is a real bound, and it is stated in\n * `docs/cli-reference.md` (\"When the `--role-arn` session expires\") rather\n * than handled: a deploy that outlives the 1-hour session fails with\n * `ExpiredTokenException`, and the remedy is to re-run the command. Kept on\n * the interface anyway because `applyRoleArnIfSet` has the value in hand and\n * it is what the `Assumed role ... (session expires ...)` line reports;\n * dropping it would make that line the only record of a fact the state of\n * the process already depends on. `assumeRoleForCrossAccountStateRead`'s\n * structurally identical `AwsCredentials` is the one that DOES act on its\n * copy — it re-assumes on a cache hit past the safety buffer.\n */\n expiration?: Date;\n}\n\n/**\n * A partial AWS SDK client config. Empty when no proxy is configured and no\n * role has been assumed.\n *\n * `credentials` is derived from `defaultProvider` rather than imported from\n * `@smithy/types` so that naming the type costs no new dependency; the second\n * member is the STATIC bag published by {@link setAssumedRoleCredentials}.\n */\nexport interface AwsClientDefaults {\n /**\n * The active per-stack scope's region (issue go-to-k/cdkd#1981), and absent\n * outside a scope. A site that names its own `region` after this spread\n * still wins, which is what a site deliberately targeting another region\n * (a replica, a state bucket) needs.\n */\n region?: string;\n requestHandler?: NodeHttpHandler;\n credentials?: ReturnType<typeof defaultProvider> | AssumedRoleCredentials | CallerEnvCredentials;\n}\n\n/**\n * The profile `@smithy/shared-ini-file-loader`'s `getProfileName` falls back to\n * when neither a client config nor `AWS_PROFILE` names one.\n *\n * Naming it EXPLICITLY is how {@link callerIdentityForOptOut} builds shape 3's\n * chain: `credential-provider-node`'s first link short-circuits whenever\n * `init.profile ?? process.env.AWS_PROFILE` is truthy, and every OTHER link it\n * feeds (`fromSSO` / `fromIni` / `fromProcess`) resolves the same profile name\n * it would have defaulted to, while `fromTokenFile` and the container / IMDS\n * providers do not read a profile at all. So passing this is EXACTLY \"the\n * default chain minus its environment link\" — the one thing that has to be\n * subtracted, expressed through the SDK's own documented precedence rather than\n * by re-spelling the chain (which would then go stale on an SDK bump).\n */\nconst SDK_DEFAULT_PROFILE = 'default';\n\n/**\n * The one warning {@link CHAIN_LOGGER} drops, matched on its distinctive line\n * rather than on the whole text: the rest of the message is advisory prose an\n * SDK bump is free to reword, while this line is the warning's identity.\n */\nconst MULTIPLE_CREDENTIAL_SOURCES_WARNING = 'Multiple credential sources detected';\n\n/**\n * Handed to shape 3's injected chain to drop exactly ONE message —\n * `credential-provider-node`'s `Multiple credential sources detected` warning —\n * while every other warning the chain emits still reaches the user.\n *\n * That warning fires on `profile set AND AWS_ACCESS_KEY_ID/SECRET set`, which\n * is exactly shape 3's inputs, because this module NAMES `default` as the\n * profile ({@link SDK_DEFAULT_PROFILE}) to subtract the env link. Its text\n * (\"Both AWS_PROFILE and the pair ... are set. This SDK will proceed with the\n * AWS_PROFILE value.\") is FALSE here: `AWS_PROFILE` is unset, the profile name\n * is one cdkd passed, and the triple is one cdkd itself wrote. Left alone it\n * goes to `console.warn`, outside cdkd's logger, on every shape-3 client.\n *\n * **Scoped to shape 3, deliberately.** Shape 1 (`AWS_PROFILE` or a `profile`\n * key selected) returns `{}`, so the client builds the chain itself with no\n * logger and the SDK still emits this warning to `console.warn`. That is left\n * alone: there the message is TRUE — a profile really is selected and really\n * does win — so only its closing advice is unhelpful, and silencing it would\n * mean injecting a logger into every client config in the tree.\n *\n * **Why not a wholesale `warn: () => {}`.** Shape 3's own population is who\n * loses real warnings: `@smithy/credential-provider-imds`'s\n * `staticStabilityProvider` warns on `Credential renew failed:` and on\n * extending an expiration, and `@aws-sdk/credential-provider-http`'s `fromHttp`\n * warns when both the relative and full container-credential URIs (or both\n * token spellings) are set. Both of those default to `console` when no logger\n * is passed, so forwarding `warn` to the console restores their baseline\n * exactly. `trace` / `debug` / `info` stay no-ops: `defaultProvider`\n * reaches those through `logger?.` and therefore emits NOTHING without a\n * logger, so forwarding them would newly print a per-link chain trace on every\n * shape-3 run.\n *\n * **`error` is DEFENSIVE, and restores no baseline** — the claim above is true\n * of `warn` alone. No link in the installed chain calls either spelling of it:\n * across `@aws-sdk/credential-provider-*`, `@aws-sdk/token-providers` and\n * `@smithy/credential-provider-imds` the logger methods invoked are\n * `logger?.debug`, `logger?.warn` and `logger.warn`, with zero `logger.error` /\n * `logger?.error` (measured 2026-09-17), and the imds `options?.logger ||\n * console` default is therefore warn-only in practice. The arm is kept so that\n * a link which starts raising one is not silenced by cdkd having supplied a\n * logger. If such a link arrives, re-decide this arm rather than assuming it is\n * still right: a new link will most likely reach the logger through\n * `logger?.error`, which is the same optional-call shape that makes `info`\n * silent without a logger — so forwarding it would print a line the SDK emits\n * for nobody today, and the `info` argument above applies to it unchanged.\n *\n * **A dropped emission is permanent, and that is a bound, not a claim of\n * harmlessness.** `multipleCredentialSourceWarningEmitted` is a module-global\n * one-shot inside `credential-provider-node`, and the SDK sets it whether or\n * not the logger printed anything — so a drop here silences any LATER\n * emission in the process, including one from a chain cdk-local builds. What\n * makes that acceptable is reachability rather than scope: a later legitimate\n * emission needs a profile selected, and a selected profile is shape 1, where\n * nothing is dropped. Only a caller that builds a shape-3 client and then\n * selects a profile in the SAME process could lose one, and no cdkd path does:\n * `buildProgram`'s `preAction` hook (`src/cli/program.ts`) mirrors `--profile`\n * into `process.env.AWS_PROFILE` BEFORE any command action runs, so a\n * `--profile` run is shape 1 at every site from its first client onward.\n *\n * **That hook is the whole argument, which is why it is named.** Only 2 of the\n * 19 opt-out sites pass `profile` to this helper, so \"a `--profile` run passes\n * the key to every site\" is NOT what makes the claim true — and moving the\n * mirror into the individual commands, or dropping it because the SDK reads the\n * flag anyway, would kill the reachability argument with nothing in THIS file's\n * tests failing. Removing the hook outright does red three cases that assert it\n * directly (`AWS_PROFILE` set before a nested command action runs, and two\n * siblings), so the tree is not silent about the hook itself — it is silent\n * about this argument depending on it.\n *\n * **A BOUND on both forwards** (go-to-k/cdkd#3003, recorded rather than fixed):\n * `console.warn` / `console.error` bypass `ConsoleLogger.formatMessage`, so the\n * extra arguments the chain passes — an SDK error object on\n * `logger.warn(\"Credential renew failed: \", e)` — skip the C0 / C1 / bidi\n * denylist every cdkd logger call inherits, and skip cdkd's level control too.\n * It is NOT a regression: without a logger those links write to `console`\n * themselves, so the baseline is the same unsanitized call. Routing through\n * `getLogger()` is what the fix would need, and it collides with the import\n * closure this file's own import block describes: this module is reachable from\n * `aws-clients.ts`, so every relative import in it must carry the `.ts`\n * spelling, and `src/utils/logger.ts` reaches `display-safe` /\n * `live-renderer` / `stack-context` through `.js` specifiers — pulling the\n * whole tree into the closure `vp run audit:coverage:check` resolves the way\n * `node` does. The same constraint is why the whitespace-proxy refusal below\n * throws a plain `Error` rather than a `CdkdError`.\n *\n * `constructor.name` must not be `NoOpLogger` or `credential-provider-node`\n * routes the warning to `console.warn` instead of here — a plain object literal\n * is what satisfies that.\n *\n * EXPORTED as a test seam only, like {@link resetAwsClientDefaults}. No\n * `src/**` module reads it THROUGH this export — the one reader is\n * `callerIdentityForOptOut` below, which hands it straight to `defaultProvider`.\n * `defaultProvider` closes over it, so `error` —\n * the arm no installed link calls — is reachable from a test by NO other route.\n * Do not mutate it: every shape-3 chain in the process shares this one object.\n */\nexport const CHAIN_LOGGER = {\n // The no-op arms take the same rest parameter the forwarding ones do. A bare\n // `() => {}` is assignable to the SDK's `(...content: any[]) => void` and so\n // works, but it types the seam as REFUSING arguments — which is false of the\n // thing being modelled (every link calls `logger?.debug(message)`), and makes\n // a test that swallows a real message a type error rather than a check.\n trace: (..._args: unknown[]): void => {},\n debug: (..._args: unknown[]): void => {},\n info: (..._args: unknown[]): void => {},\n warn: (...args: unknown[]): void => {\n if (\n args.some(\n (arg) => typeof arg === 'string' && arg.includes(MULTIPLE_CREDENTIAL_SOURCES_WARNING)\n )\n ) {\n return;\n }\n console.warn(...args);\n },\n error: (...args: unknown[]): void => {\n console.error(...args);\n },\n};\n\n/**\n * The proxy variables, in the order `proxy-from-env` itself reads them.\n *\n * Only their PRESENCE is decided here. Which one applies to a given request —\n * and whether `NO_PROXY` exempts it — is `getProxyForUrl`'s job, per request,\n * inside {@link ProxyRoutingAgent}. Deciding it here instead would collapse\n * `HTTP_PROXY` and `HTTPS_PROXY` into one answer and send `http://` traffic to\n * an HTTPS proxy.\n */\nexport const PROXY_ENV_VARS = [\n 'HTTPS_PROXY',\n 'https_proxy',\n 'HTTP_PROXY',\n 'http_proxy',\n 'ALL_PROXY',\n 'all_proxy',\n] as const;\n\nlet proxyConfigured: boolean | undefined;\n\n/**\n * The credentials `--role-arn` assumed for THIS process, or `undefined` when no\n * role was assumed. Set once, at the command's entry, by `applyRoleArnIfSet`.\n *\n * Process-global for the same reason the `AWS_*` env vars it replaces were:\n * `--role-arn` is a process-wide decision taken before any stack work starts,\n * so there is nothing per-stack or per-concurrent-task to key it on. It is\n * monotonic — set once and never unset in production — so a client built before\n * it is set and one built after cannot disagree about which identity the user\n * chose; only about whether the role had been assumed yet, and every command\n * assumes it before constructing any other client. That is ENFORCED, not\n * merely intended: {@link setAssumedRoleCredentials} refuses a second publish.\n */\nlet assumedRoleCredentials: AssumedRoleCredentials | undefined;\n\n/**\n * The `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` triple\n * the process was started with, captured by `applyRoleArnIfSet` IMMEDIATELY\n * BEFORE it overwrites those variables with the assumed role's.\n *\n * `undefined` means one of two different things, and the caller must not\n * conflate them: no role has been assumed at all (ask\n * {@link getAssumedRoleCredentials}), or a role was assumed and the caller had\n * NO static credentials in its environment — the SSO / IMDS / container-role\n * chains, where the triple was absent before the assume and there is nothing to\n * restore. `src/utils/caller-credentials.ts` is the one place that reads the two\n * together and turns them into an answer.\n *\n * Process-global for the same reason {@link assumedRoleCredentials} is: this is\n * the state of the environment at a single process-wide decision point.\n */\nlet preAssumeEnvCredentials: CallerEnvCredentials | undefined;\n\n/**\n * Static AWS credentials read out of (or destined for) the process environment.\n *\n * Distinct from {@link AssumedRoleCredentials} in exactly one field:\n * `sessionToken` is OPTIONAL here, because a long-lived IAM user key has none\n * while `sts:AssumeRole` always issues one.\n */\nexport interface CallerEnvCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n}\n\n/**\n * The message BOTH ends of the single-publish invariant refuse with — this\n * module's {@link setAssumedRoleCredentials} and `role-arn.ts`'s\n * `applyRoleArnIfSet`, which trips first so that nothing is corrupted before\n * the refusal (see that function's comment).\n *\n * Shared rather than spelled twice: the two were byte-identical copies, so\n * rewording one would silently leave a user facing two different explanations\n * of one invariant depending on which end caught them — and the test that\n * matches the text would keep passing against the stale copy.\n */\nexport const SECOND_ROLE_PUBLISH_REFUSAL =\n 'An assumed role has already been published for this process. ' +\n '`--role-arn` is a single process-wide decision taken once at a command entry; ' +\n 'publishing a second role would leave clients built before and after it running as ' +\n 'different identities.';\n\n/**\n * Publish the credentials `--role-arn` assumed, so every later SDK client\n * built through {@link awsClientDefaults} runs as the role.\n *\n * Called by `applyRoleArnIfSet` AFTER the `sts:AssumeRole` hop completes — the\n * base credentials (a profile, the env, an instance role) must still be the\n * ones answering that hop.\n *\n * **A SECOND publish is REFUSED.** {@link assumedRoleCredentials}'s doc calls\n * the value monotonic, and every consumer relies on it: a client built before a\n * second publish and one built after would run as DIFFERENT identities inside\n * one process, with nothing naming which. `applyRoleArnIfSet` is meant to run\n * once at a command's entry, so a second call is a wiring defect — and the\n * failure it produces without this refusal is silent and account-scoped, which\n * is the class issue [#3130](https://github.com/go-to-k/cdkd/issues/3130)\n * exists to close. `resetAwsClientDefaults()` is the only way back, and it is a\n * TEST seam. A plain `Error` rather than `CdkdError` for this module's usual\n * reason: importing the error module would add a relative `.js` import to the\n * closure `scripts/audit-provider-coverage.ts` resolves literally.\n *\n * The bag is COPIED rather than stored by reference. The same object is handed\n * to every client `awsClientDefaults` serves, so aliasing the caller's would\n * let whoever still holds it rewrite the identity of clients already built —\n * the reason `AwsClients.credentialConfig` clones for its own siblings.\n */\nexport function setAssumedRoleCredentials(credentials: AssumedRoleCredentials): void {\n if (assumedRoleCredentials !== undefined) {\n throw new Error(SECOND_ROLE_PUBLISH_REFUSAL);\n }\n assumedRoleCredentials = {\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n sessionToken: credentials.sessionToken,\n ...(credentials.expiration && { expiration: credentials.expiration }),\n };\n}\n\n/**\n * Record the caller's own pre-assume environment credentials, or `undefined`\n * when the environment carried none.\n *\n * Called by `applyRoleArnIfSet` BEFORE it overwrites the `AWS_*` triple —\n * afterwards the value is unrecoverable, and it is the only identity the\n * `cdkd local` surface may hand to an emulated workload.\n *\n * COPIED rather than stored by reference, for {@link setAssumedRoleCredentials}'s\n * reason and with more reach than it had before: since shape 2 of the\n * `ignoreAssumedRole` opt-out reads this snapshot, it is the source of the\n * identity EVERY opted-out client runs as, and {@link getPreAssumeEnvCredentials}\n * hands it out. Aliasing the caller's object would let whoever still holds it\n * rewrite that identity after the fact.\n */\nexport function setPreAssumeEnvCredentials(credentials: CallerEnvCredentials | undefined): void {\n preAssumeEnvCredentials = credentials\n ? {\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n ...(credentials.sessionToken !== undefined && { sessionToken: credentials.sessionToken }),\n }\n : undefined;\n}\n\n/**\n * The credentials recorded by {@link setPreAssumeEnvCredentials}, if any.\n *\n * Read by `src/utils/caller-credentials.ts` — the local surface's env channel —\n * and by tests. Read it THROUGH that module rather than directly: `undefined`\n * here is ambiguous on its own (see {@link preAssumeEnvCredentials}).\n *\n * The returned bag is this module's OWN copy, not the caller's — do not mutate\n * it; shape 2 of the opt-out resolves every opted-out client's identity from\n * the same object.\n */\nexport function getPreAssumeEnvCredentials(): CallerEnvCredentials | undefined {\n return preAssumeEnvCredentials;\n}\n\n/**\n * The credentials published by {@link setAssumedRoleCredentials}, if any.\n *\n * ONE production consumer, and it asks a yes/no question rather than using the\n * value: `applyRoleArnIfSet` reads it to refuse a second `--role-arn` BEFORE\n * its `AssumeRole` hop, so that nothing is corrupted first. No client ever\n * reads it — every one of those receives the credentials through\n * {@link awsClientDefaults} — so this is not a way to \"get the role\" and must\n * not become one; a site that wants the role's identity simply omits\n * `ignoreAssumedRole`.\n */\nexport function getAssumedRoleCredentials(): AssumedRoleCredentials | undefined {\n return assumedRoleCredentials;\n}\n\nfunction isProxyConfigured(): boolean {\n // Read lazily and memoize only the PARSE RESULT, so module import order\n // cannot freeze an answer before the CLI has finished setting up, and so a\n // test can control the environment through `resetAwsClientDefaults()`.\n if (proxyConfigured === undefined) {\n // Every spelling is examined before the answer is decided. A `.some()` here\n // short-circuited at the first VALID variable, so `HTTPS_PROXY=http://ok`\n // beside a typo'd `http_proxy=' '` never reached the guard below -- and\n // the typo then resurfaced per request as exactly the unnamed URL-parse\n // error the guard exists to pre-empt.\n let configured = false;\n for (const name of PROXY_ENV_VARS) {\n const value = process.env[name];\n if (value === undefined || value === '') continue;\n if (value.trim() === '') {\n // Whitespace-only is a typo, not a configuration. Treating it as SET\n // fails later with a URL-parse error naming neither the variable nor\n // cdkd; treating it as UNSET is worse still, because the run then goes\n // direct and fails with the certificate error this whole change exists\n // to remove -- with no hint that the variable was the cause.\n //\n // A plain `Error` rather than `CdkdError`: importing the error module\n // would add a `.js` relative import to the closure reachable from\n // `aws-clients.ts`, which is the constraint the header above describes.\n throw new Error(\n `${name} is set to whitespace only. Set it to a proxy URL ` +\n `(e.g. http://proxy.example:8080) or unset it.`\n );\n }\n configured = true;\n }\n proxyConfigured = configured;\n }\n return proxyConfigured;\n}\n\n/**\n * The identity a client that passed `ignoreAssumedRole` must run as, or\n * `undefined` when the SDK's own chain already resolves the caller and nothing\n * needs injecting.\n *\n * Only called once a role HAS been published — see\n * {@link AwsClientDefaultsOptions.ignoreAssumedRole} for the three shapes and\n * why shape 3 cannot be left to the default chain.\n *\n * `process.env` is READ here and never written. Clearing the triple around the\n * client construction would be the obvious alternative and is wrong: cdkd\n * builds clients concurrently (`--concurrency`, parallel stacks), so a window\n * in which the triple is absent is a window another task resolves through.\n *\n * TWO BOUNDS, RECORDED RATHER THAN CLOSED\n *\n * 1. **A `[default]` profile that sources the environment defeats shape 3.**\n * Subtracting the chain's own env link does not subtract `fromEnv` reached\n * THROUGH another link, and `credential_source = Environment` in the\n * `[default]` profile is exactly that — so the opt-out resolves the ROLE's\n * triple again. Its sibling is `credential_process`: the ini format has no\n * environment interpolation, so a templated `aws_access_key_id` is not a\n * vector, but a helper process that reads `AWS_ACCESS_KEY_ID` out of the\n * environment it inherits and echoes it back defeats the subtraction\n * identically. It is a narrow case and it is not a regression: before this\n * shape existed the same configuration simply resolved the role too. Closing\n * it would mean neutering `process.env` around the resolution, which the\n * paragraph above rules out on concurrency grounds.\n * 2. **Shapes 2 and 3 inject an explicit `credentials`, which outranks a\n * `profile` key a site sets AFTER the spread.** Shape 1 exists to hand the\n * profile back to the SDK, but it only sees a profile passed through\n * {@link AwsClientDefaultsOptions.profile} or `AWS_PROFILE` — a site that\n * passes neither and then writes `profile: x` into its own client literal\n * gets shape 2/3's credentials instead, silently. No site does that today\n * (all 19 opt-out sites audited at PR #3223), and no fence catches it: the\n * local-surface fence asks whether a site DECLARED an identity, not which\n * keys its literal carries. Pass the profile to this helper, not only to the\n * client.\n */\nfunction callerIdentityForOptOut(\n options: AwsClientDefaultsOptions,\n requestHandler: NodeHttpHandler | undefined\n): AwsClientDefaults['credentials'] | undefined {\n // Shape 1. The client-config key outranks the environment, and either one\n // makes `credential-provider-node` skip its env link, so the chain the client\n // builds for itself already answers with the caller's profile.\n const profile =\n options.profile !== undefined && options.profile !== ''\n ? options.profile\n : process.env['AWS_PROFILE'];\n if (profile !== undefined && profile !== '') return undefined;\n\n // Shape 2. COPIED rather than aliased, for {@link setAssumedRoleCredentials}'s\n // reason: the value is handed to every client built through this helper.\n const caller = preAssumeEnvCredentials;\n if (caller) {\n return {\n accessKeyId: caller.accessKeyId,\n secretAccessKey: caller.secretAccessKey,\n ...(caller.sessionToken && { sessionToken: caller.sessionToken }),\n };\n }\n\n // Shape 3. The caller's real chain, minus the env link now holding the role.\n return defaultProvider({\n profile: SDK_DEFAULT_PROFILE,\n logger: CHAIN_LOGGER,\n ...(requestHandler && { clientConfig: { requestHandler } }),\n });\n}\n\n/**\n * Returns `{}` when no proxy is configured, no role has been assumed, and no\n * opt-out is in play — which keeps that path byte-identical to what the SDK\n * builds on its own and makes this a no-op for every existing user.\n */\nexport function awsClientDefaults(options: AwsClientDefaultsOptions = {}): AwsClientDefaults {\n // Inside a per-stack scope, a client built with no region of its own must\n // take the STACK's region, not whatever `process.env.AWS_REGION` says: the\n // environment is shared by every stack deploying concurrently, and the SDK\n // memoizes the region a client resolves at its first request (issue\n // go-to-k/cdkd#1981). Spread FIRST so the credential / handler keys below are\n // never shadowed and a site's own explicit `region` still overrides it.\n const scopeRegion = currentStackAwsScope()?.region;\n const defaults = credentialAndHandlerDefaults(options);\n return scopeRegion === undefined ? defaults : { region: scopeRegion, ...defaults };\n}\n\nfunction credentialAndHandlerDefaults(options: AwsClientDefaultsOptions): AwsClientDefaults {\n // Read the proxy environment FIRST in both branches: its whitespace-only\n // guard is a refusal the assumed-role path must not skip past.\n const proxied = isProxyConfigured();\n // ONE reading of the flag, used by both decisions below. They were spelled\n // differently — a truthiness test and an `=== true` — which is equivalent\n // for a `boolean | undefined` but invites the two to drift into disagreeing\n // about the same input, and \"did this client opt out?\" must have exactly one\n // answer per call.\n const optedOut = options.ignoreAssumedRole === true;\n const assumed = optedOut ? undefined : assumedRoleCredentials;\n // The opt-out only raises a question once a role has actually been published.\n // Before that the default chain IS the caller's, so the common path keeps\n // returning `{}` and no chain is built for it.\n const optedOutOfPublishedRole = optedOut && assumedRoleCredentials !== undefined;\n\n if (!proxied) {\n if (assumed) return { credentials: assumed };\n const caller = optedOutOfPublishedRole\n ? callerIdentityForOptOut(options, undefined)\n : undefined;\n return caller ? { credentials: caller } : {};\n }\n\n // A FRESH agent per call. `NodeHttpHandler.destroy()` destroys `httpAgent`\n // and `httpsAgent` unconditionally, and Node's `Agent.destroy()` aborts\n // ACTIVE sockets, so a shared agent would let one client's teardown kill\n // another client's in-flight request. See `proxy-routing-agent.ts`.\n const agent = new ProxyRoutingAgent();\n const requestHandler = new NodeHttpHandler({ httpAgent: agent, httpsAgent: agent });\n\n if (assumed) {\n // No chain to inject: the assumed credentials are already resolved, so\n // nothing here has to reach STS or an SSO portal. The handler is still\n // returned — it is what routes the SERVICE calls through the proxy.\n return { requestHandler, credentials: assumed };\n }\n\n // The proxied arm needs the SAME three shapes: it is a separate return, and\n // its `credentials` is populated in both polarities (it carries the injected\n // chain), so a call-site fence that only looks at the returned KEYS is blind\n // here.\n const caller = optedOutOfPublishedRole\n ? callerIdentityForOptOut(options, requestHandler)\n : undefined;\n if (caller) return { requestHandler, credentials: caller };\n\n return {\n requestHandler,\n credentials: defaultProvider({\n ...(options.profile !== undefined && options.profile !== '' && { profile: options.profile }),\n clientConfig: { requestHandler },\n }),\n };\n}\n\n/**\n * Drop the memoized environment read, the published assumed-role credentials,\n * AND the pre-assume caller snapshot. Test seam; mirrors `resetAwsClients()`.\n *\n * All three are dropped together because all three are process-global state\n * this module owns, and a role leaking from one test into the next would\n * silently change which identity every client in the next test resolves — or,\n * for the snapshot, which identity the `cdkd local` surface hands the emulated\n * workload.\n */\nexport function resetAwsClientDefaults(): void {\n proxyConfigured = undefined;\n assumedRoleCredentials = undefined;\n preAssumeEnvCredentials = undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,MAAM,sBAAsB;CAAE,WAAW;CAAM,YAAY;AAAG;;;;;;;;;AAU9D,SAAS,WAAW,SAAmC;CACrD,MAAM,SAAS,QAAQ;CACvB,MAAM,UAAU,QAAQ,QAAQ;CAChC,MAAM,OAAO,QAAQ,SAAS,GAAG,KAAK,CAAC,QAAQ,WAAW,GAAG,IAAI,IAAI,QAAQ,KAAK;CAClF,MAAM,OAAO,QAAQ,SAAS,SAAS,MAAM;CAC7C,OAAO,GAAG,SAAS,UAAU,OAAO,KAAK,KAAK,GAAG;AACnD;AAEA,IAAa,oBAAb,cAAuCA,QAAM;;;;;;;;;CAS3C,AAAiB,8BAAc,IAAI,IAAuB;CAE1D,QAAQ,MAAqB,SAAsC;EACjE,MAAM,SAAS,QAAQ;EACvB,MAAM,WAAW,eAAe,WAAW,OAAO,CAAC;EACnD,MAAM,MAAM,GAAG,SAAS,UAAU,OAAO,GAAG,YAAY;EAExD,MAAM,SAAS,KAAK,YAAY,IAAI,GAAG;EACvC,IAAI,QAAQ,OAAO;EAEnB,MAAM,QAAQ,iBAAiB,QAAQ,QAAQ;EAC/C,KAAK,YAAY,IAAI,KAAK,KAAK;EAC/B,OAAO;CACT;CAEA,AAAS,UAAgB;EACvB,KAAK,MAAM,SAAS,KAAK,YAAY,OAAO,GAC1C,MAAM,QAAQ;EAEhB,KAAK,YAAY,MAAM;EACvB,MAAM,QAAQ;CAChB;AACF;AAEA,SAAS,iBAAiB,QAAiB,UAA6B;CACtE,IAAI,CAAC,UAIH,OAAO,SAAS,IAAIC,QAAW,mBAAmB,IAAI,IAAIC,MAAU,mBAAmB;CAOzF,OAAO,SACH,IAAI,gBAAwB,UAAU,mBAAmB,IACzD,IAAI,eAAuB,UAAU,mBAAmB;AAC9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AClFA,MAAM,UAAU,IAAI,kBAAiC;;;;;AAMrD,SAAgB,mBAAsB,OAAsB,IAAgB;CAC1E,IAAI,OAAO,MAAM,WAAW,YAAY,MAAM,WAAW,IAIvD,MAAM,IAAI,MAAM,gDAAgD;CAElE,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;;AAGA,SAAgB,uBAAkD;CAChE,OAAO,QAAQ,SAAS;AAC1B;;;;;;;;;AAUA,SAAgB,gBAAoC;CAClD,OAAO,QAAQ,SAAS,CAAC,EAAE,UAAU,QAAQ,IAAI;AACnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC+IA,MAAM,sBAAsB;;;;;;AAO5B,MAAM,sCAAsC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkG5C,MAAa,eAAe;CAM1B,QAAQ,GAAG,UAA2B,CAAC;CACvC,QAAQ,GAAG,UAA2B,CAAC;CACvC,OAAO,GAAG,UAA2B,CAAC;CACtC,OAAO,GAAG,SAA0B;EAClC,IACE,KAAK,MACF,QAAQ,OAAO,QAAQ,YAAY,IAAI,SAAS,mCAAmC,CACtF,GAEA;EAEF,QAAQ,KAAK,GAAG,IAAI;CACtB;CACA,QAAQ,GAAG,SAA0B;EACnC,QAAQ,MAAM,GAAG,IAAI;CACvB;AACF;;;;;;;;;;AAWA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,IAAI;;;;;;;;;;;;;;AAeJ,IAAI;;;;;;;;;;;;;;;;;AAkBJ,IAAI;;;;;;;;;;;;AA0BJ,MAAa,8BACX;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BF,SAAgB,0BAA0B,aAA2C;CACnF,IAAI,2BAA2B,QAC7B,MAAM,IAAI,MAAM,2BAA2B;CAE7C,yBAAyB;EACvB,aAAa,YAAY;EACzB,iBAAiB,YAAY;EAC7B,cAAc,YAAY;EAC1B,GAAI,YAAY,cAAc,EAAE,YAAY,YAAY,WAAW;CACrE;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,2BAA2B,aAAqD;CAC9F,0BAA0B,cACtB;EACE,aAAa,YAAY;EACzB,iBAAiB,YAAY;EAC7B,GAAI,YAAY,iBAAiB,UAAa,EAAE,cAAc,YAAY,aAAa;CACzF,IACA;AACN;;;;;;;;;;;;AAaA,SAAgB,6BAA+D;CAC7E,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,4BAAgE;CAC9E,OAAO;AACT;AAEA,SAAS,oBAA6B;CAIpC,IAAI,oBAAoB,QAAW;EAMjC,IAAI,aAAa;EACjB,KAAK,MAAM,QAAQ,gBAAgB;GACjC,MAAM,QAAQ,QAAQ,IAAI;GAC1B,IAAI,UAAU,UAAa,UAAU,IAAI;GACzC,IAAI,MAAM,KAAK,MAAM,IAUnB,MAAM,IAAI,MACR,GAAG,KAAK,gGAEV;GAEF,aAAa;EACf;EACA,kBAAkB;CACpB;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAS,wBACP,SACA,gBAC8C;CAI9C,MAAM,UACJ,QAAQ,YAAY,UAAa,QAAQ,YAAY,KACjD,QAAQ,UACR,QAAQ,IAAI;CAClB,IAAI,YAAY,UAAa,YAAY,IAAI,OAAO;CAIpD,MAAM,SAAS;CACf,IAAI,QACF,OAAO;EACL,aAAa,OAAO;EACpB,iBAAiB,OAAO;EACxB,GAAI,OAAO,gBAAgB,EAAE,cAAc,OAAO,aAAa;CACjE;CAIF,OAAO,gBAAgB;EACrB,SAAS;EACT,QAAQ;EACR,GAAI,kBAAkB,EAAE,cAAc,EAAE,eAAe,EAAE;CAC3D,CAAC;AACH;;;;;;AAOA,SAAgB,kBAAkB,UAAoC,CAAC,GAAsB;CAO3F,MAAM,cAAc,qBAAqB,CAAC,EAAE;CAC5C,MAAM,WAAW,6BAA6B,OAAO;CACrD,OAAO,gBAAgB,SAAY,WAAW;EAAE,QAAQ;EAAa,GAAG;CAAS;AACnF;AAEA,SAAS,6BAA6B,SAAsD;CAG1F,MAAM,UAAU,kBAAkB;CAMlC,MAAM,WAAW,QAAQ,sBAAsB;CAC/C,MAAM,UAAU,WAAW,SAAY;CAIvC,MAAM,0BAA0B,YAAY,2BAA2B;CAEvE,IAAI,CAAC,SAAS;EACZ,IAAI,SAAS,OAAO,EAAE,aAAa,QAAQ;EAC3C,MAAM,SAAS,0BACX,wBAAwB,SAAS,MAAS,IAC1C;EACJ,OAAO,SAAS,EAAE,aAAa,OAAO,IAAI,CAAC;CAC7C;CAMA,MAAM,QAAQ,IAAI,kBAAkB;CACpC,MAAM,iBAAiB,IAAI,gBAAgB;EAAE,WAAW;EAAO,YAAY;CAAM,CAAC;CAElF,IAAI,SAIF,OAAO;EAAE;EAAgB,aAAa;CAAQ;CAOhD,MAAM,SAAS,0BACX,wBAAwB,SAAS,cAAc,IAC/C;CACJ,IAAI,QAAQ,OAAO;EAAE;EAAgB,aAAa;CAAO;CAEzD,OAAO;EACL;EACA,aAAa,gBAAgB;GAC3B,GAAI,QAAQ,YAAY,UAAa,QAAQ,YAAY,MAAM,EAAE,SAAS,QAAQ,QAAQ;GAC1F,cAAc,EAAE,eAAe;EACjC,CAAC;CACH;AACF"}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as isVersionOnlyInvocation, t as getCdkdVersion } from "./version-ChuDKy_E.js";
2
+ import { n as isVersionOnlyInvocation, t as getCdkdVersion } from "./version-DVA14XyG.js";
3
3
 
4
4
  //#region src/cli/pipe-close-handler.ts
5
5
  /**
@@ -74,7 +74,7 @@ async function main() {
74
74
  console.log(getCdkdVersion());
75
75
  return;
76
76
  }
77
- const { buildProgram } = await import("./program-C5r-kmIQ.js");
77
+ const { buildProgram } = await import("./program-CAdTYHWf.js");
78
78
  const program = buildProgram();
79
79
  const args = reorderArgs(process.argv);
80
80
  await program.parseAsync(args);