@go-to-k/cdkd 0.291.26 → 0.291.27

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.
@@ -8,6 +8,47 @@ import { HttpProxyAgent } from "http-proxy-agent";
8
8
  import { HttpsProxyAgent } from "https-proxy-agent";
9
9
  import { getProxyForUrl } from "proxy-from-env";
10
10
 
11
+ //#region src/utils/url-authority.ts
12
+ /**
13
+ * The authority component of a URL (`<host>:<port>`), for every place cdkd
14
+ * composes one from a host it was handed.
15
+ *
16
+ * A bare `${host}:${port}` is wrong for exactly one shape of host: an IPv6
17
+ * literal, which RFC 3986 3.2.2 requires to be bracketed. `--host ::` otherwise
18
+ * prints `http://:::3000`, and a `fetch()` built the same way throws
19
+ * `Invalid URL` before any request is sent (go-to-k/cdkd#2338, mirroring
20
+ * go-to-k/cdk-local#599, whose `src/utils/url-authority.ts` this follows).
21
+ */
22
+ /**
23
+ * Hex digits, `:`, and `.` for the IPv4-mapped form (`::ffff:127.0.0.1`). Not a
24
+ * full grammar: the job is only to tell an address from a colon-bearing string
25
+ * that is not one, which must pass through untouched rather than gain brackets.
26
+ */
27
+ const IPV6_LITERAL_CHARS = /^[0-9A-Fa-f:.]+$/;
28
+ /**
29
+ * Render `host` as it must appear inside a URL authority.
30
+ *
31
+ * - An IPv6 literal is bracketed; an already-bracketed one is bracketed exactly
32
+ * once, so a `URL.hostname` fed back in never becomes `[[::1]]`.
33
+ * - A zone id (`fe80::1%en0`) is DROPPED: no URL parser accepts one, raw or
34
+ * percent-encoded, so propagating it would emit the unparseable authority
35
+ * this helper exists to prevent.
36
+ * - Everything else (IPv4, a registered name, the empty string) is returned as-is.
37
+ */
38
+ function formatHostForAuthority(host) {
39
+ const inner = host.length > 1 && host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
40
+ if (!inner.includes(":")) return host;
41
+ const zoneAt = inner.indexOf("%");
42
+ const literal = zoneAt === -1 ? inner : inner.slice(0, zoneAt);
43
+ if (!IPV6_LITERAL_CHARS.test(literal)) return host;
44
+ return `[${literal}]`;
45
+ }
46
+ /** `<host>:<port>` for use inside a URL, bracketing an IPv6 literal. */
47
+ function formatAuthority(host, port) {
48
+ return `${formatHostForAuthority(host)}:${port}`;
49
+ }
50
+
51
+ //#endregion
11
52
  //#region src/utils/proxy-routing-agent.ts
12
53
  /**
13
54
  * An `http.Agent` that routes each request through the environment's proxy, or
@@ -72,8 +113,7 @@ const INNER_AGENT_OPTIONS = {
72
113
  */
73
114
  function requestUrl(options) {
74
115
  const secure = options.secureEndpoint;
75
- const rawHost = options.host ?? "localhost";
76
- const host = rawHost.includes(":") && !rawHost.startsWith("[") ? `[${rawHost}]` : rawHost;
116
+ const host = formatHostForAuthority(options.host ?? "localhost");
77
117
  const port = options.port ?? (secure ? 443 : 80);
78
118
  return `${secure ? "https" : "http"}://${host}:${port}`;
79
119
  }
@@ -638,5 +678,5 @@ function credentialAndHandlerDefaults(options) {
638
678
  }
639
679
 
640
680
  //#endregion
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
681
+ 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, formatAuthority as u };
682
+ //# sourceMappingURL=aws-client-defaults-qSDe1fA_.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws-client-defaults-qSDe1fA_.js","names":["Agent","HttpsAgent","HttpAgent"],"sources":["../src/utils/url-authority.ts","../src/utils/proxy-routing-agent.ts","../src/utils/stack-aws-scope.ts","../src/utils/aws-client-defaults.ts"],"sourcesContent":["/**\n * The authority component of a URL (`<host>:<port>`), for every place cdkd\n * composes one from a host it was handed.\n *\n * A bare `${host}:${port}` is wrong for exactly one shape of host: an IPv6\n * literal, which RFC 3986 3.2.2 requires to be bracketed. `--host ::` otherwise\n * prints `http://:::3000`, and a `fetch()` built the same way throws\n * `Invalid URL` before any request is sent (go-to-k/cdkd#2338, mirroring\n * go-to-k/cdk-local#599, whose `src/utils/url-authority.ts` this follows).\n */\n\n/**\n * Hex digits, `:`, and `.` for the IPv4-mapped form (`::ffff:127.0.0.1`). Not a\n * full grammar: the job is only to tell an address from a colon-bearing string\n * that is not one, which must pass through untouched rather than gain brackets.\n */\nconst IPV6_LITERAL_CHARS = /^[0-9A-Fa-f:.]+$/;\n\n/**\n * Render `host` as it must appear inside a URL authority.\n *\n * - An IPv6 literal is bracketed; an already-bracketed one is bracketed exactly\n * once, so a `URL.hostname` fed back in never becomes `[[::1]]`.\n * - A zone id (`fe80::1%en0`) is DROPPED: no URL parser accepts one, raw or\n * percent-encoded, so propagating it would emit the unparseable authority\n * this helper exists to prevent.\n * - Everything else (IPv4, a registered name, the empty string) is returned as-is.\n */\nexport function formatHostForAuthority(host: string): string {\n const wrapped = host.length > 1 && host.startsWith('[') && host.endsWith(']');\n const inner = wrapped ? host.slice(1, -1) : host;\n if (!inner.includes(':')) return host;\n const zoneAt = inner.indexOf('%');\n const literal = zoneAt === -1 ? inner : inner.slice(0, zoneAt);\n if (!IPV6_LITERAL_CHARS.test(literal)) return host;\n return `[${literal}]`;\n}\n\n/** `<host>:<port>` for use inside a URL, bracketing an IPv6 literal. */\nexport function formatAuthority(host: string, port: number | string): string {\n return `${formatHostForAuthority(host)}:${port}`;\n}\n","/**\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';\nimport { formatHostForAuthority } from './url-authority.ts';\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 host = formatHostForAuthority(options.host ?? 'localhost');\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":"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"}
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-BkoR4hMq.js";
2
+ import { n as isVersionOnlyInvocation, t as getCdkdVersion } from "./version-CuKFdkKV.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-BALgPCWL.js");
77
+ const { buildProgram } = await import("./program-WCBWtvb0.js");
78
78
  const program = buildProgram();
79
79
  const args = reorderArgs(process.argv);
80
80
  await program.parseAsync(args);
@@ -1,6 +1,6 @@
1
1
  import { C as displayAwsMessage, D as isPasteableIdent, E as displayStackName, O as truncateCodePoints, S as UNRENDERABLE, T as displaySafe, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, w as displayIdent, x as STACK_REF_MAX_CODE_POINTS, y as withoutGeneratedFallbackName } from "./logger-Bt0RGDfb.js";
2
- import { a as setAssumedRoleCredentials, c as currentStackAwsScope, i as getPreAssumeEnvCredentials, l as runInStackAwsScope, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, s as ambientRegion, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-jISU4PHo.js";
3
- import { t as getCdkdVersion } from "./version-BkoR4hMq.js";
2
+ import { a as setAssumedRoleCredentials, c as currentStackAwsScope, i as getPreAssumeEnvCredentials, l as runInStackAwsScope, n as awsClientDefaults, o as setPreAssumeEnvCredentials, r as getAssumedRoleCredentials, s as ambientRegion, t as SECOND_ROLE_PUBLISH_REFUSAL } from "./aws-client-defaults-qSDe1fA_.js";
3
+ import { t as getCdkdVersion } from "./version-CuKFdkKV.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -1235,10 +1235,13 @@ function isUpdateUnsupportedError(error, logicalId) {
1235
1235
  /**
1236
1236
  * The way cdkd builds a `cdkd ...` command it tells an operator to PASTE.
1237
1237
  *
1238
- * Thirteen message modules route through it today. The CATEGORIES it does not
1239
- * cover yet are at the end of this header — categories, not a census: the
1240
- * population is what go-to-k/cdkd#3436's greps return against the tree of the
1241
- * day, and a list here would be read as complete and go stale.
1238
+ * DERIVE its callers rather than reading a number here — `grep -rln
1239
+ * 'pasteableCommand(' src/` is the census, and the one this sentence used to
1240
+ * carry went stale twice in one PR, most recently when a site moved to
1241
+ * `commandHole`. The CATEGORIES it does not cover yet are at the end of this
1242
+ * header — categories, not a census, for the same reason: the population is
1243
+ * what go-to-k/cdkd#3436's greps return against the tree of the day, and a
1244
+ * list here would be read as complete.
1242
1245
  *
1243
1246
  * go-to-k/cdkd#3363 measured the class this module closes, and
1244
1247
  * [#3436](https://github.com/go-to-k/cdkd/issues/3436) records it repo-wide.
@@ -1298,14 +1301,21 @@ function isUpdateUnsupportedError(error, logicalId) {
1298
1301
  * same way; they are not this function, so a rule change reaches them only by
1299
1302
  * hand.
1300
1303
  * - **A command in prose quotes with a RAW value**, outside the modules
1301
- * migrated here — `provisioning/providers/**` (S3 Tables, Route 53, DynamoDB),
1304
+ * migrated here — `provisioning/providers/**` (Route 53, DynamoDB),
1302
1305
  * `cli/config-loader.ts` and `cli/commands/orphan.ts` are where the greps land
1303
1306
  * today.
1304
- * - **The `cdkd drift` sites of
1305
- * [#3307](https://github.com/go-to-k/cdkd/issues/3307)**, which take their own
1306
- * gate in that PR. Note a `cdkd drift` command also appears OUTSIDE that file.
1307
- * - **A spelled-out template or usage synopsis printing a BARE `<hole>`**, and
1308
- * the source fence that would find them.
1307
+ *
1308
+ * Three entries left this list in go-to-k/cdkd#3613 and saying so is the point:
1309
+ * the `cdkd drift` sites of
1310
+ * [#3307](https://github.com/go-to-k/cdkd/issues/3307) ALL build through this
1311
+ * function now and `drift.ts`'s own `stackCommandFor` is gone; the S3 Tables
1312
+ * provider's raw-value prose was fixed there too; and a bare-`<hole>` synopsis
1313
+ * is no longer uncovered, because the source fence that finds that shape
1314
+ * EXISTS — `scripts/check-pasteable-command-shapes.ts`, whose unit test is its
1315
+ * enforcement. A list of what is not yet covered goes stale the moment
1316
+ * something is, so derive it rather than reading it: the fence reports the
1317
+ * shapes, and its `EXEMPTIONS` name the sites deliberately left for a
1318
+ * follow-up PR.
1309
1319
  */
1310
1320
  /**
1311
1321
  * Quote a value for a pasteable shell command.
@@ -1342,7 +1352,9 @@ function commandHole(name) {
1342
1352
  * WHY a value must become a hole, or `undefined` when it may be NAMED.
1343
1353
  *
1344
1354
  * FIRST MATCH WINS, in the order written: `empty`, `altered`, `too-long`,
1345
- * `option-shaped`, `pattern-shaped` (m21 of the go-to-k/cdkd#3499 review). A
1355
+ * `option-shaped`, `pattern-shaped`, `not-plain` (m21 of the go-to-k/cdkd#3499
1356
+ * review; the sixth is M17 of go-to-k/cdkd#3613's, and sits last because it
1357
+ * subsumes the other five — see its member note). A
1346
1358
  * value can satisfy several — `-\u001b[x` is both option-shaped and altered —
1347
1359
  * and the caller renders ONE sentence, so the order decides which. It runs
1348
1360
  * cheapest-and-most-fundamental first: a value that does not survive rendering
@@ -1359,6 +1371,7 @@ function withholdReason(value, opts) {
1359
1371
  if (truncateCodePoints(safe, 1152).truncated) return "too-long";
1360
1372
  if (value.startsWith("-")) return "option-shaped";
1361
1373
  if (opts?.patternMatched === true && (value.includes("*") || value.includes("/"))) return "pattern-shaped";
1374
+ if (opts?.plainIdent === true && !isPasteableIdent(value)) return "not-plain";
1362
1375
  }
1363
1376
  /**
1364
1377
  * Build a pasteable `cdkd` command: every user-controlled value shell-quoted
@@ -1405,6 +1418,81 @@ function pasteableCommand(verb, args = [], extraFlags = []) {
1405
1418
  withheld
1406
1419
  };
1407
1420
  }
1421
+ /**
1422
+ * The sentence for a name `pasteableCommand` would not print, rendered from the
1423
+ * REASON it gave rather than from a predicate of this site's own — M11 of the
1424
+ * go-to-k/cdkd#3499 review. Keying it on a second predicate got both directions
1425
+ * wrong at once: `Old;Stack` renders exactly, so a rendering-based clause called
1426
+ * a command that names it "not exact", and `--all` also renders exactly, so the
1427
+ * same clause said nothing above a hole the operator was then invited to fill
1428
+ * with the name printed beside it.
1429
+ *
1430
+ * SCOPE: the sentence is about a STATE RECORD's name — it opens "This record's
1431
+ * name" and ends by pointing at `cdkd state list --long` — so it fits a caller
1432
+ * whose value names a state record and nothing else. Every live caller does:
1433
+ * the legacy-key migrate refusals in `drift.ts` and `state.ts`, and
1434
+ * `state orphan`'s `Destroy with:` warning, whose positional must equal a
1435
+ * record's stack name. A caller naming anything else needs its own sentence,
1436
+ * not this one with a different verb.
1437
+ *
1438
+ * WHICH reasons are reachable is the CALLER's question, not this function's,
1439
+ * and every arm is answered here because the gate can return any of them. For
1440
+ * a name that comes from an S3 key segment — `state refresh-observed`'s legacy
1441
+ * refusal and `drift`'s — four of the six are reachable and two are bounded
1442
+ * out by the key itself:
1443
+ *
1444
+ * - `altered`, `option-shaped` and `pattern-shaped` are all reachable: a
1445
+ * planted key can spell a name any of those ways in a handful of bytes.
1446
+ * - `not-plain` is reachable only from a caller passing `plainIdent` —
1447
+ * `drift`'s site (M17 of the go-to-k/cdkd#3613 review) and every `state.ts`
1448
+ * caller (go-to-k/cdkd#3696) — and there it is the reason for
1449
+ * every name the five arms above admit but `isPasteableIdent` refuses:
1450
+ * `$(printf INJECTED)`, or a padded name spelling a labelled line.
1451
+ * - `empty` is not. `listStacks` drops a key whose stack segment is empty
1452
+ * (`s3-state-backend.ts`'s `if (!stackName) continue`), so `target.stackName`
1453
+ * is non-empty by the time the refusal is built.
1454
+ * - `too-long` is not either, and the bound is not obvious: the cap is
1455
+ * `STACK_REF_MAX_CODE_POINTS` (1152), while S3 caps the WHOLE key at 1024
1456
+ * bytes, leaving at most 1008 for the name inside `cdkd/<name>/state.json`.
1457
+ * A multi-byte character only lowers the code-point count further, so a name
1458
+ * arriving through `listStacks` can never reach the cap.
1459
+ *
1460
+ * Both unreachable arms stay, and are covered, because the REASON is the
1461
+ * gate's and not this site's: `pasteableCommand` can return either to a caller
1462
+ * whose value comes from somewhere else. A missing arm is no longer a silent
1463
+ * fall-through — the `switch` below is exhaustive over `WithholdReason`, so
1464
+ * dropping one is a compile error (M13). What the cases buy on top of that is
1465
+ * the SENTENCE: the type checker knows an arm exists, not that it says the
1466
+ * right thing, and substituting one arm's text for another's is exactly the
1467
+ * disagreement M11 closed.
1468
+ */
1469
+ function withheldTargetClause(built, hole, verb) {
1470
+ const reason = built.withheld.find((w) => w.hole === hole)?.reason;
1471
+ if (reason === void 0) return "";
1472
+ let why;
1473
+ switch (reason) {
1474
+ case "altered":
1475
+ why = `does NOT render exactly, so another record may render identically`;
1476
+ break;
1477
+ case "empty":
1478
+ why = `is empty`;
1479
+ break;
1480
+ case "too-long":
1481
+ why = `is too long to print`;
1482
+ break;
1483
+ case "option-shaped":
1484
+ why = `begins with a '-', so it is not safe to print as an argument to '${verb}' — a name like '--all' is parsed as the FLAG and targets every stack`;
1485
+ break;
1486
+ case "pattern-shaped":
1487
+ why = `would be read as a PATTERN by '${verb}', which can match other stacks`;
1488
+ break;
1489
+ case "not-plain":
1490
+ why = "is not a plain identifier (a letter or digit, then letters, digits, '~', '_', '.' or '-'), the only shape named in a command here, since a name outside it can run as shell or read as a line of this message once the terminal wraps";
1491
+ break;
1492
+ default: throw new Error(`withheldTargetClause: unhandled WithholdReason ${String(reason)}`);
1493
+ }
1494
+ return ` This record's name ${why} — so it is not named in the command below; list the records as stored with 'cdkd state list --long' and act on the one whose key matches.`;
1495
+ }
1408
1496
 
1409
1497
  //#endregion
1410
1498
  //#region src/utils/error-handler.ts
@@ -14885,9 +14973,12 @@ function recordedExpressionsOf(secrets) {
14885
14973
  * value would refuse — a template that deployed before this feature — which is
14886
14974
  * a regression rather than a trade.
14887
14975
  *
14888
- * WHAT IT IS. `stack + region + output key -> the plaintext that key held
14889
- * before redaction`, written at the moment the producer's outputs are redacted
14890
- * and read at the three cross-stack sites above. It answers only for a producer
14976
+ * WHAT IT IS. `credential identity + stack + region + output key -> the
14977
+ * plaintext that key held before redaction` (the identity since
14978
+ * go-to-k/cdkd#3691: the writer's `credentialFingerprint`, which every reader
14979
+ * computes the same way from the clients it resolves with), written at the
14980
+ * moment the producer's outputs are redacted and read at the three cross-stack
14981
+ * sites above. It answers only for a producer
14891
14982
  * THIS PROCESS deployed in THIS run, which is exactly the population that has a
14892
14983
  * plaintext to hand back: a separate `cdkd deploy` of the consumer has none, and
14893
14984
  * that case is refused rather than guessed at.
@@ -14913,15 +15004,15 @@ function recordedExpressionsOf(secrets) {
14913
15004
  * value for the WIRE, never for persistence.
14914
15005
  */
14915
15006
  const recoverableMaskedOutputs = /* @__PURE__ */ new Map();
14916
- function maskedOutputKey(stackName, region, outputKey) {
14917
- return `${stackName}\u0000${region}\u0000${outputKey}`;
15007
+ function maskedOutputKey(identity, stackName, region, outputKey) {
15008
+ return `${identity}\u0000${stackName}\u0000${region}\u0000${outputKey}`;
14918
15009
  }
14919
15010
  /**
14920
15011
  * Remember the plaintext an output held before {@link SECRET_MASK} replaced it.
14921
15012
  * See {@link recoverableMaskedOutputs}.
14922
15013
  */
14923
- function recordRecoverableMaskedOutput(stackName, region, outputKey, plaintext) {
14924
- recoverableMaskedOutputs.set(maskedOutputKey(stackName, region, outputKey), plaintext);
15014
+ function recordRecoverableMaskedOutput(identity, stackName, region, outputKey, plaintext) {
15015
+ recoverableMaskedOutputs.set(maskedOutputKey(identity, stackName, region, outputKey), plaintext);
14925
15016
  }
14926
15017
  /**
14927
15018
  * The plaintext this process masked out of `stackName`'s `outputKey`, or
@@ -14930,8 +15021,8 @@ function recordRecoverableMaskedOutput(stackName, region, outputKey, plaintext)
14930
15021
  * `undefined` is the honest answer for a producer deployed by an EARLIER run:
14931
15022
  * the value is gone and cdkd must refuse rather than write the mask to AWS.
14932
15023
  */
14933
- function recoverMaskedOutput(stackName, region, outputKey) {
14934
- return recoverableMaskedOutputs.get(maskedOutputKey(stackName, region, outputKey));
15024
+ function recoverMaskedOutput(identity, stackName, region, outputKey) {
15025
+ return recoverableMaskedOutputs.get(maskedOutputKey(identity, stackName, region, outputKey));
14935
15026
  }
14936
15027
  const crossStackAssociations = /* @__PURE__ */ new WeakMap();
14937
15028
  /** Poison for a key seen against two different (expression, plaintext) pairs. */
@@ -24415,10 +24506,21 @@ function inspectTail(prose, stackName, region, recovery) {
24415
24506
  * ONE spelling, because copies are what drift, and they carry it byte-identically
24416
24507
  * on purpose — a reader who has met one must recognise the others. Two wrap it
24417
24508
  * in {@link DROP_RECORD_LINE}'s label; `divergentRecordRegionRefusalMessage`
24418
- * ends on it bare. `<stack>` / `<region>` are LITERAL: nothing may substitute them, for
24509
+ * ends on it bare. The two holes are LITERAL: nothing may substitute them, for
24419
24510
  * the reason {@link malformedDestroyResourcesRefusalMessage}'s note gives.
24511
+ *
24512
+ * They are {@link commandHole}'s QUOTED form rather than a bare `<stack>`, and
24513
+ * the difference is not cosmetic. A bare `<name>` is two shell redirections:
24514
+ * pasted, `<stack` reads stdin from a file and the `>` takes the NEXT WORD as
24515
+ * an output target and CREATES it. This template is inert today only because
24516
+ * it ends on its second hole, so the trailing `>` faces the newline and bash
24517
+ * refuses the line — a property of where the command happens to stop, not a
24518
+ * decision anyone made, and it goes away the moment a flag is appended. That
24519
+ * is how go-to-k/cdkd#3363 met this shape (M4 of its review). Measured on
24520
+ * go-to-k/cdkd#3436: the quoted form passes both holes through as literal
24521
+ * argv (`ARGV: state orphan <stack> --stack-region <region>`).
24420
24522
  */
24421
- const DROP_RECORD_TEMPLATE = "cdkd state orphan <stack> --stack-region <region>";
24523
+ const DROP_RECORD_TEMPLATE = `cdkd state orphan ${commandHole("stack")} --stack-region ${commandHole("region")}`;
24422
24524
  /**
24423
24525
  * The same template as a LABELLED line, for the two messages that carry their
24424
24526
  * commands one per line. The COMMAND is the atom rather than the line, because
@@ -27191,7 +27293,7 @@ var DiffCalculator = class DiffCalculator {
27191
27293
  if (change.changeType === "NO_CHANGE") {
27192
27294
  change.changeType = "UPDATE";
27193
27295
  change.propertyChanges = syntheticChanges;
27194
- this.logger.debug(`UPDATE (in-place attr propagated): ${dependentId} reads a changed attribute of an updated resource`);
27296
+ this.logger.debug(`UPDATE (in-place attr propagated): ${dependentId} reads a value that may move in this deploy`);
27195
27297
  } else change.propertyChanges = [...change.propertyChanges ?? [], ...syntheticChanges];
27196
27298
  }
27197
27299
  return added;
@@ -30216,8 +30318,9 @@ const REF_RETURNS_SEGMENT_AFTER_PIPE = /* @__PURE__ */ new Set([
30216
30318
  * (`[<refId>, <parentId>]` — segment order REVERSED vs the after-pipe family),
30217
30319
  * so CFn's `Ref` value is the segment BEFORE the FIRST pipe (issue #963
30218
30320
  * family audit; both confirmed against the AWS-docs "Return values / Ref"
30219
- * section):
30220
- * - AWS::ApiGateway::Deployment `<deploymentId>|<restApiId>` -> deployment id
30321
+ * section). Examples — the Set below is the complete list, each entry with its
30322
+ * own reason:
30323
+ * - AWS::ApiGateway::Deployment `<deploymentId>|<restApiId>` -> deployment id
30221
30324
  * - AWS::ApiGateway::DocumentationPart `<docPartId>|<restApiId>` -> documentation part id
30222
30325
  * - AWS::ApiGatewayV2::Authorizer `<authorizerId>|<apiId>` -> authorizer id
30223
30326
  * - AWS::ApiGatewayV2::ApiMapping `<apiMappingId>|<domainName>` -> api mapping id
@@ -30243,7 +30346,8 @@ const REF_RETURNS_SEGMENT_BEFORE_FIRST_PIPE = /* @__PURE__ */ new Set([
30243
30346
  "AWS::ApiGateway::DocumentationPart",
30244
30347
  "AWS::ApiGatewayV2::Authorizer",
30245
30348
  "AWS::ApiGatewayV2::ApiMapping",
30246
- "AWS::ECS::Service"
30349
+ "AWS::ECS::Service",
30350
+ "AWS::EC2::VPCCidrBlock"
30247
30351
  ]);
30248
30352
  /**
30249
30353
  * SDK-provisioned types whose provider stores the resource ARN as the physical
@@ -34334,7 +34438,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
34334
34438
  */
34335
34439
  async reresolveCrossStackValue(value, producerRegion, context, origin, sourceKey, producerOutput, producerRegionLogText) {
34336
34440
  if (carriesSecretMask(value)) {
34337
- const recovered = producerOutput === void 0 || producerOutput.crossAccount === true ? void 0 : recoverMaskedOutput(producerOutput.stackName, producerOutput.region, producerOutput.outputKey);
34441
+ const recovered = producerOutput === void 0 || producerOutput.crossAccount === true ? void 0 : recoverMaskedOutput(credentialFingerprint(ambientCredentialConfig()), producerOutput.stackName, producerOutput.region, producerOutput.outputKey);
34338
34442
  if (recovered !== void 0) {
34339
34443
  if (context.recordedSecretValues) recordFreshNoEchoValuesIn(recovered, context.recordedSecretValues);
34340
34444
  return recovered;
@@ -38190,7 +38294,7 @@ var CloudControlProvider = class {
38190
38294
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
38191
38295
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
38192
38296
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
38193
- const { ASGProvider } = await import("./asg-provider-Dbqbr89e.js").then((n) => n.n);
38297
+ const { ASGProvider } = await import("./asg-provider-CWCv370K.js").then((n) => n.n);
38194
38298
  const asgProvider = new ASGProvider();
38195
38299
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
38196
38300
  }
@@ -50193,8 +50297,8 @@ function deepEqualValue(a, b) {
50193
50297
  * test performs, not a number anybody has to maintain.
50194
50298
  */
50195
50299
  function crossStackReadsForPartialSave(previous, recordedImports, recordedOutputReads, normalizeName) {
50196
- const importKey = (e) => `${e.sourceStack}\u0000${canonicalizeRegion(e.sourceRegion)}\u0000${normalizeName(e.exportName)}`;
50197
- const outputReadKey = (e) => `${normalizeName(e.sourceStack)}\u0000${canonicalizeRegion(e.sourceRegion)}\u0000${normalizeName(e.outputName)}`;
50300
+ const importKey = (e) => injectiveKey(e.sourceStack, canonicalizeRegion(e.sourceRegion), normalizeName(e.exportName));
50301
+ const outputReadKey = (e) => injectiveKey(normalizeName(e.sourceStack), canonicalizeRegion(e.sourceRegion), normalizeName(e.outputName));
50198
50302
  const imports = unionCrossStackReads(previous.imports, recordedImports, importKey);
50199
50303
  const outputReads = unionCrossStackReads(previous.outputReads, recordedOutputReads, outputReadKey);
50200
50304
  return {
@@ -50660,11 +50764,12 @@ var DeployEngine = class DeployEngine {
50660
50764
  */
50661
50765
  rememberRecoverableMaskedOutputs(stackName, resolved, redacted) {
50662
50766
  if (resolved === redacted) return;
50767
+ const identity = credentialFingerprint(ambientCredentialConfig());
50663
50768
  for (const [key, redactedValue] of Object.entries(redacted)) {
50664
50769
  if (!carriesSecretMask(redactedValue)) continue;
50665
50770
  const plaintext = resolved[key];
50666
50771
  if (plaintext === void 0 || carriesSecretMask(plaintext)) continue;
50667
- recordRecoverableMaskedOutput(stackName, this.stackRegion, key, plaintext);
50772
+ recordRecoverableMaskedOutput(identity, stackName, this.stackRegion, key, plaintext);
50668
50773
  }
50669
50774
  }
50670
50775
  /**
@@ -50947,7 +51052,7 @@ var DeployEngine = class DeployEngine {
50947
51052
  { literal: "--force" }
50948
51053
  ]).command}`);
50949
51054
  }
50950
- if (withheld.length > 0) parts.push(`Re-import the record that HOLDS the mask for ${withheld.map(shown).join(", ")}, but the command is withheld: that is not a plain CloudFormation logical id, so a pasted 'cdkd import <stack> --resource <id>=<physicalId> --force' could be reshaped by the shell or name a different resource. Read the id from 'cdkd state show' and quote it yourself.`);
51055
+ if (withheld.length > 0) parts.push(`Re-import the record that HOLDS the mask for ${withheld.map(shown).join(", ")}, but the command is withheld: that is not a plain CloudFormation logical id, so a pasted cdkd import line could be reshaped by the shell or name a different resource. Read the id from 'cdkd state show' and quote it yourself, in cdkd import ${commandHole("stack")} --resource ${commandHole("id")}=${commandHole("physicalId")} --force.`);
50951
51056
  }
50952
51057
  if (unclearableTargets.length > 0) parts.push(`Do NOT re-import ${unclearableTargets.map(shown).join(", ")}: a custom resource's import records no attributes, so the masked bag is carried forward unchanged and the refusal repeats. Cause (1) above is the one that applies to it.`);
50953
51058
  if (foreignReads.length > 0) {
@@ -51017,7 +51122,8 @@ var DeployEngine = class DeployEngine {
51017
51122
  * Re-read a STALE record's attributes from AWS (issue
51018
51123
  * [#1852](https://github.com/go-to-k/cdkd/issues/1852)) — the resolver calls
51019
51124
  * this, through `ResolverContext.attributeHealer`, only when `Fn::GetAtt` is
51020
- * about to take the physical-id fallback.
51125
+ * about to take the physical-id fallback, or reaches one of the resolver's
51126
+ * heal-first arms (issue [#3627](https://github.com/go-to-k/cdkd/issues/3627)).
51021
51127
  *
51022
51128
  * The read is the provider's `import()` with `knownPhysicalId` — the same
51023
51129
  * primitive `orphan-adoption.ts` verifies a record with. It is READ-ONLY by
@@ -51034,7 +51140,7 @@ var DeployEngine = class DeployEngine {
51034
51140
  */
51035
51141
  healStaleAttributes(logicalId, resource, stackName) {
51036
51142
  if (!this.isHealEligible(logicalId, resource)) return Promise.resolve({ kind: "not-attempted" });
51037
- const key = `${logicalId}\u0000${resource.physicalId}`;
51143
+ const key = injectiveKey(logicalId, resource.physicalId);
51038
51144
  const inFlight = this.attributeHeals.get(key);
51039
51145
  if (inFlight) return inFlight;
51040
51146
  const heal = this.readStaleAttributes(logicalId, resource, stackName).catch((error) => ({
@@ -51159,7 +51265,7 @@ var DeployEngine = class DeployEngine {
51159
51265
  *
51160
51266
  * REDACTION HAPPENS HERE, at the persist choke point, and must not move to
51161
51267
  * record time: `crossStackReadsForPartialSave` / `unionCrossStackReads` dedup
51162
- * on `${sourceStack}\0${region}\0${name}`, so changing a value mid-run makes
51268
+ * on the (source stack, region, name) triple, so changing a value mid-run makes
51163
51269
  * the union write BOTH spellings, and its first-seen-wins merge would keep
51164
51270
  * whichever arrived first.
51165
51271
  *
@@ -53594,5 +53700,5 @@ var DeployEngine = class DeployEngine {
53594
53700
  };
53595
53701
 
53596
53702
  //#endregion
53597
- export { yellow as $, CdkdError as $a, synthesisStatusMessage as $i, refuseMalformedOrphanRecordsForDestroy as $n, forceQuitRecoveryClause as $r, configIntegerRefusal as $t, createPreDeleteFinalSnapshot as A, credentialFingerprint as Aa, validateContainerRepoName as Ai, hasReadableOutputs as An, markRedactedCause as Ao, TEMPLATE_SOURCED_RULES as Ar, deleteSkipReason as At, isStatefulRecreateTargetSync as B, absoluteAssemblyPathEscape as Ba, getDockerCmd as Bi, malformedOrphansRefusalMessage as Bn, maskSecretsInText as Br, isImpossibleEmptyStoredAttribute as Bt, namesAnyDeclaredParameter as C, expectedOwnerParam as Ca, assertAssetBucketRegion as Ci, STATE_REGION_DIVERGED as Cn, pasteableCommand as Co, describeTypeWithThrottleRetry as Cr, compositeIdSkipResult as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, ambientClientDefaults as Da, parseBootstrapMarker as Di, deployRefusesOrphanRowsReason as Dn, isThrottlingError as Do, STATE_SOURCED_BASELINE_RULES as Dr, maskerOrIdentity as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, derivePartitionAndUrlSuffix as Ea, isCrossRegionRedirect as Ei, UNREADABLE_RESOURCES_MAP_ROW as En, isRetryableTransientError as Eo, TemplateParser as Er, maskDeep as Et, extractDeploymentEventError as F, defineOwnKey as Fa, describeDockerExecFailure as Fi, malformedExportSourceWarning as Fn, identityKeyFor as Fr, carriesDynamicReference as Ft, toCfnInteger as G, clearBucketRegionCache as Ga, runDockerForeground as Gi, malformedResourceEntriesWarning as Gn, redactSecretsForState as Gr, isSettledInstanceState as Gt, WARM_THROUGHPUT_MEMBERS as H, displayAssemblyPath as Ha, isMalformedEnvKey as Hi, malformedOutputsRefusalMessage as Hn, recordMaskOnlyValue as Hr, isUnboundTemplateParameter as Ht, makeSiblingClaimReader as I, hasOwnKey as Ia, describeDockerFailure as Ii, malformedLocalOutputsWarning as In, isSingleDynamicReferenceToken as Ir, carriesFetchableDynamicReference as It, bold as J, getAwsClients as Ja, escapeRegExp$1 as Ji, malformedStateRefusalMessage as Jn, S3StateBackend as Jr, resolveExplicitPhysicalId as Jt, toFiniteNumber as K, resolveBucketRegion as Ka, runDockerStreaming as Ki, malformedResourcePropertiesWarning as Kn, scrubResourceRecord as Kr, WAFv2WebACLProvider as Kt, planOrphanAdoption as L, hasPlainPrototype$1 as La, dockerSpawnEnvWithSensitive as Li, malformedOrphanRecordsRefusalMessage as Ln, isUncertifiedBaselineMaskPosition as Lr, cfnRefValueFromPhysicalId as Lt, refusesFinalSnapshot as M, failedStageNote as Ma, buildDockerImage as Mi, isReadableOrphanRecord as Mn, describeAwsFailure as Mo, createSecretMasker as Mr, disableInstanceApiTermination as Mt, unsupportedFinalSnapshotError as N, globMatches as Na, resolveDockerContextDirectory as Ni, isReadableResourceEntry as Nn, safeStringify$1 as No, dynamicReferenceTokens as Nr, isTerminationProtectionPropagationError as Nt, buildFinalSnapshotIdentifier as O, ambientCredentialConfig as Oa, readBootstrapMarkerBody as Oi, displayLogicalId as On, isTransientServerError as Oo, STATE_SOURCED_CROSS_GENERATION_RULES as Or, UNSPECIFIED_SKIP_REASON as Ot, makeCanonicalizePropertiesFn as P, processStackMessages as Pa, describeDockerCapturedOutput as Pi, malformedExportNamesWarning as Pn, __exportAll as Po, errorCauseChain as Pr, IntrinsicFunctionResolver as Pt, red as Q, AssetError as Qa, Synthesizer as Qi, refuseMalformedOrphanRecords as Qn, buildLockContentionMessage as Qr, configBooleanRefusal as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, nullPrototypeRecord as Ra, finchSecretArgvRefusal as Ri, malformedOrphanRecordsWarning as Rn, markSameGenerationBag as Rr, coerceParameterTypedValue as Rt, computeParameterTaint as S, decodeListingKey as Sa, BOOTSTRAP_MARKER_PREFIX as Si, INTRINSIC_KEYS as Sn, withErrorHandling as So, findSilentDropProperties as Sr, compositeIdSeparatorRefusal as St, resourcesNamingDeclaredParameter as T, canonicalizeRegion as Ta, getBootstrapMarkerKey as Ti, UNREADABLE_ORPHANS_CONTAINER_ROW as Tn, isMarkedNonRetryable as To, DagBuilder as Tr, createMaskedRetryLogger as Tt, coerceWarmThroughput as U, renderAssemblyPathEscape as Ua, partitionSensitiveEnv as Ui, malformedOutputsWarning as Un, recordMaskOnlyValuesIn as Ur, parameterTypeMayLoseSecretIdentity as Ut, renderStatefulReason as V, describeFileReadFailure as Va, isDockerClientEnvKey as Vi, malformedOrphansWarning as Vn, pathCrossesDottedKey as Vr, isNamelessDynamicReferenceError as Vt, isWarmThroughputDecrease as W, resolveAssemblyPath as Wa, redactDockerArgvValues as Wi, malformedRenderedContainersWarning as Wn, recoverMaskedOutput as Wr, refStateLookupFromResource as Wt, gray as X, runWithStackAwsClients as Xa, AssetManifestLoader as Xi, refuseDivergentRecordRegionForDestroy as Xn, UNREPRODUCIBLE_LOCK_CLAUSE as Xr, coerceCfnBoolean as Xt, cyan as Y, resetAwsClients as Ya, stripControlChars as Yi, previewableOrphanRecords as Yn, rebuildClientForBucketRegion as Yr, assertRegionMatch as Yt, green as Z, setAwsClients as Za, getDockerImageBySourceHash as Zi, refuseMalformedNestedChildOutputs as Zn, buildForceUnlockCommand as Zr, coerceCfnInteger as Zt, bagHoldsSecretExpression as _, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as _a, buildAssetRedirectMap as _i, AWS_CREDENTIAL_ENV_KEYS as _n, StateError as _o, safeRegion as _r, startInterruptWatch as _t, DeploymentEventsStore as a, resolveSkipPrefix as aa, hasReadableExportSet as ai, requireConfigString as an, IntrinsicResolutionRefusalError as ao, refuseMalformedResourceAttributesForOrphan as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, producerCoordinateKey as ba, rewriteTemplateAssetReferences as bi, envCredentialsAreAssumedRole as bn, isCdkdError as bo, unreadableOrphanRecords as br, ccProtectionRegistryTypes as bt, replayFailedOperations as c, resolveUseCdkBootstrapAssets as ca, importableOutputKeys as ci, classifyReplaySecretRegion as cn, LockError as co, refuseMalformedResourcePropertiesForOrphan as cr, IAMRoleProvider as ct, updatePartialMessage as d, ssmResolvedValueType as da, orphansAfterRollback as di, s3BucketArn as dn, PartialFailureError as do, repairMalformedOrphanRecordsForReadOnly as dr, ProviderRegistry as dt, getDefaultStateBucketName as ea, formatLockExpiry as ei, configStringRefusal as en, ConfigError as eo, refuseMalformedOrphans as er, collectDeclaredOutputNames as et, updatePartialReason as f, CFN_TEMPLATE_BODY_LIMIT as fa, orphansCarriedFrom as fi, s3BucketDomainName as fn, ProvisioningError as fo, repairMalformedOrphansForReadOnly as fr, wouldReturnToSdkProvider as ft, computeImplicitDeleteEdges as g, uploadCfnTemplate as ga, WorkGraph as gi, applyRoleArnIfSet as gn, StackTerminationProtectionError as go, repairMalformedResourcesForReadOnly as gr, isInterruptedWaitError as gt, IMPLICIT_DELETE_DEPENDENCIES as h, findLargeInlineResources as ha, stringifyValue as hi, s3BucketWebsiteUrl as hn, StackHasActiveImportsError as ho, repairMalformedResourcePropertiesForReadOnly as hr, interruptWatchListenerCount as ht, DeploymentEventsReader as i, resolveCaptureObservedState as ia, exportNamesCarriedFrom as ii, requireConfigObject as in, DynamicReferenceRegionAmbiguousError as io, refuseMalformedOutputsForDestroy as ir, secretBearing as it, isFinalSnapshotError as j, AssemblyReader as ja, buildDenyExternalAccessPolicy as ji, hasReadableResources as jn, retryClassificationText as jo, carriesSecretMask as jr, isWaitAbandonedError as jt, ccRoutedFinalSnapshotError as k, clientDefaultsFor as ka, validateAssetBucketName as ki, hasReadableOrphans as kn, markNonRetryable as ko, STATE_SOURCED_READBACK_RULES as kr, deleteIndeterminateGuards as kt, replayRollback as l, stateBucketExistenceConfirmed as la, importableOutputs as li, producerRegionsFromState as ln, MalformedProducerRecordRefusalError as lo, refuseMalformedResourcesForDestroy as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, MIGRATE_TMP_PREFIX as ma, AssetPublisher as mi, s3BucketRegionalDomainName as mn, ResourceUpdateNotSupportedError as mo, repairMalformedResourceEntriesForReadOnly as mr, endCommandInterruptScope as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, resolveApp as na, DEFAULT_STATE_PREFIX as ni, replayWarn as nn, DependencyError as no, refuseMalformedOrphansForOrphan as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, resolveStateBucketWithDefault as oa, hasReasonlessBaselineRefusal as oi, definedAttributes as on, LocalInvokeBuildError as oo, refuseMalformedResourceEntries as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, CFN_TEMPLATE_URL_LIMIT as pa, shouldRetainResource as pi, s3BucketDualStackDomainName as pn, ResourceTimeoutError as po, repairMalformedOutputsForReadOnly as pr, beginCommandInterruptScope as pt, formatResourceLine as q, AwsClients as qa, warnFinchArgvExposure as qi, malformedResourcesWarning as qn, LockManager as qr, normalizeAwsTagsToCfn as qt, DeployEngine as r, resolveAutoAssetStorage as ra, describeRegionValueKind as ri, requireConfigArray as rn, DeployCancelledError as ro, refuseMalformedOutputs as rr, isExportAliasCollision as rt, planRollback as s, resolveStateBucketWithDefaultAndSource as sa, hasUnverifiableParameterRefusal as si, stringifyIfAssigned as sn, LocalStartServiceError as so, refuseMalformedResourceEntriesForOrphan as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, getLegacyStateBucketName as ta, CUSTOM_RESOURCE_RESPONSE_PREFIX as ti, readConfigString as tn, CrossAccountSecretRefusalError as to, refuseMalformedOrphansForDestroy as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, warnDeprecatedNoPrefixCliFlag as ua, isReadableBag as ui, withSharedDrainBudget as un, NestedStackChildDirectDestroyError as uo, refuseMalformedState as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, injectiveKey as va, createAssetRedirectResolver as vi, applyCallerIdentityCredentials as vn, SynthesisError as vo, safeStackName as vr, CloudControlProvider as vt, reachableDivergentParameters as w, PARTITION_TABLE as wa, ensureAssetStorage as wi, STATE_RESOURCES_MALFORMED as wn, shellQuote as wo, withRetry as wr, packCompositeId as wt, bindingSkippedOutputs as x, producerRecordKey as xa, AssetModeResolver as xi, DiffCalculator as xn, normalizeAwsError as xo, findActionableSilentDrops as xr, compositeIdFormatMessage as xt, keptWholeReasonText as y, injectiveKeyPrefix as ya, loadPublishableAssetManifest as yi, callerEnvCredentials as yn, formatError as yo, unpreviewableOrphanRecords as yr, slowCcOperationTimeoutMs as yt, isStatefulRecreateTargetForReplace as z, ownValue as za, formatDockerLoginError as zi, malformedOrphanRowsKeptWarning as zn, maskSecretsInError as zr, getAccountInfo as zt };
53598
- //# sourceMappingURL=deploy-engine-BZps_IKn.js.map
53703
+ export { yellow as $, CdkdError as $a, synthesisStatusMessage as $i, refuseMalformedOrphanRecordsForDestroy as $n, forceQuitRecoveryClause as $r, configIntegerRefusal as $t, createPreDeleteFinalSnapshot as A, credentialFingerprint as Aa, validateContainerRepoName as Ai, hasReadableOutputs as An, isTransientServerError as Ao, TEMPLATE_SOURCED_RULES as Ar, deleteSkipReason as At, isStatefulRecreateTargetSync as B, absoluteAssemblyPathEscape as Ba, getDockerCmd as Bi, malformedOrphansRefusalMessage as Bn, maskSecretsInText as Br, isImpossibleEmptyStoredAttribute as Bt, namesAnyDeclaredParameter as C, expectedOwnerParam as Ca, assertAssetBucketRegion as Ci, STATE_REGION_DIVERGED as Cn, commandHole as Co, describeTypeWithThrottleRetry as Cr, compositeIdSkipResult as Ct, PRE_DELETE_SNAPSHOT_TYPES as D, ambientClientDefaults as Da, parseBootstrapMarker as Di, deployRefusesOrphanRowsReason as Dn, isMarkedNonRetryable as Do, STATE_SOURCED_BASELINE_RULES as Dr, maskerOrIdentity as Dt, ATOMIC_FINAL_SNAPSHOT_TYPES as E, derivePartitionAndUrlSuffix as Ea, isCrossRegionRedirect as Ei, UNREADABLE_RESOURCES_MAP_ROW as En, withheldTargetClause as Eo, TemplateParser as Er, maskDeep as Et, extractDeploymentEventError as F, defineOwnKey as Fa, describeDockerExecFailure as Fi, malformedExportSourceWarning as Fn, safeStringify$1 as Fo, identityKeyFor as Fr, carriesDynamicReference as Ft, toCfnInteger as G, clearBucketRegionCache as Ga, runDockerForeground as Gi, malformedResourceEntriesWarning as Gn, redactSecretsForState as Gr, isSettledInstanceState as Gt, WARM_THROUGHPUT_MEMBERS as H, displayAssemblyPath as Ha, isMalformedEnvKey as Hi, malformedOutputsRefusalMessage as Hn, recordMaskOnlyValue as Hr, isUnboundTemplateParameter as Ht, makeSiblingClaimReader as I, hasOwnKey as Ia, describeDockerFailure as Ii, malformedLocalOutputsWarning as In, __exportAll as Io, isSingleDynamicReferenceToken as Ir, carriesFetchableDynamicReference as It, bold as J, getAwsClients as Ja, escapeRegExp$1 as Ji, malformedStateRefusalMessage as Jn, S3StateBackend as Jr, resolveExplicitPhysicalId as Jt, toFiniteNumber as K, resolveBucketRegion as Ka, runDockerStreaming as Ki, malformedResourcePropertiesWarning as Kn, scrubResourceRecord as Kr, WAFv2WebACLProvider as Kt, planOrphanAdoption as L, hasPlainPrototype$1 as La, dockerSpawnEnvWithSensitive as Li, malformedOrphanRecordsRefusalMessage as Ln, isUncertifiedBaselineMaskPosition as Lr, cfnRefValueFromPhysicalId as Lt, refusesFinalSnapshot as M, failedStageNote as Ma, buildDockerImage as Mi, isReadableOrphanRecord as Mn, markRedactedCause as Mo, createSecretMasker as Mr, disableInstanceApiTermination as Mt, unsupportedFinalSnapshotError as N, globMatches as Na, resolveDockerContextDirectory as Ni, isReadableResourceEntry as Nn, retryClassificationText as No, dynamicReferenceTokens as Nr, isTerminationProtectionPropagationError as Nt, buildFinalSnapshotIdentifier as O, ambientCredentialConfig as Oa, readBootstrapMarkerBody as Oi, displayLogicalId as On, isRetryableTransientError as Oo, STATE_SOURCED_CROSS_GENERATION_RULES as Or, UNSPECIFIED_SKIP_REASON as Ot, makeCanonicalizePropertiesFn as P, processStackMessages as Pa, describeDockerCapturedOutput as Pi, malformedExportNamesWarning as Pn, describeAwsFailure as Po, errorCauseChain as Pr, IntrinsicFunctionResolver as Pt, red as Q, AssetError as Qa, Synthesizer as Qi, refuseMalformedOrphanRecords as Qn, buildLockContentionMessage as Qr, configBooleanRefusal as Qt, MULTI_REGION_RECREATE_BLOCKED_TYPES as R, nullPrototypeRecord as Ra, finchSecretArgvRefusal as Ri, malformedOrphanRecordsWarning as Rn, markSameGenerationBag as Rr, coerceParameterTypedValue as Rt, computeParameterTaint as S, decodeListingKey as Sa, BOOTSTRAP_MARKER_PREFIX as Si, INTRINSIC_KEYS as Sn, withErrorHandling as So, findSilentDropProperties as Sr, compositeIdSeparatorRefusal as St, resourcesNamingDeclaredParameter as T, canonicalizeRegion as Ta, getBootstrapMarkerKey as Ti, UNREADABLE_ORPHANS_CONTAINER_ROW as Tn, shellQuote as To, DagBuilder as Tr, createMaskedRetryLogger as Tt, coerceWarmThroughput as U, renderAssemblyPathEscape as Ua, partitionSensitiveEnv as Ui, malformedOutputsWarning as Un, recordMaskOnlyValuesIn as Ur, parameterTypeMayLoseSecretIdentity as Ut, renderStatefulReason as V, describeFileReadFailure as Va, isDockerClientEnvKey as Vi, malformedOrphansWarning as Vn, pathCrossesDottedKey as Vr, isNamelessDynamicReferenceError as Vt, isWarmThroughputDecrease as W, resolveAssemblyPath as Wa, redactDockerArgvValues as Wi, malformedRenderedContainersWarning as Wn, recoverMaskedOutput as Wr, refStateLookupFromResource as Wt, gray as X, runWithStackAwsClients as Xa, AssetManifestLoader as Xi, refuseDivergentRecordRegionForDestroy as Xn, UNREPRODUCIBLE_LOCK_CLAUSE as Xr, coerceCfnBoolean as Xt, cyan as Y, resetAwsClients as Ya, stripControlChars as Yi, previewableOrphanRecords as Yn, rebuildClientForBucketRegion as Yr, assertRegionMatch as Yt, green as Z, setAwsClients as Za, getDockerImageBySourceHash as Zi, refuseMalformedNestedChildOutputs as Zn, buildForceUnlockCommand as Zr, coerceCfnInteger as Zt, bagHoldsSecretExpression as _, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as _a, buildAssetRedirectMap as _i, AWS_CREDENTIAL_ENV_KEYS as _n, StateError as _o, safeRegion as _r, startInterruptWatch as _t, DeploymentEventsStore as a, resolveSkipPrefix as aa, hasReadableExportSet as ai, requireConfigString as an, IntrinsicResolutionRefusalError as ao, refuseMalformedResourceAttributesForOrphan as ar, secretBearingStateKeyWarning as at, mergeNoChangeOutputs as b, producerCoordinateKey as ba, rewriteTemplateAssetReferences as bi, envCredentialsAreAssumedRole as bn, isCdkdError as bo, unreadableOrphanRecords as br, ccProtectionRegistryTypes as bt, replayFailedOperations as c, resolveUseCdkBootstrapAssets as ca, importableOutputKeys as ci, classifyReplaySecretRegion as cn, LockError as co, refuseMalformedResourcePropertiesForOrphan as cr, IAMRoleProvider as ct, updatePartialMessage as d, ssmResolvedValueType as da, orphansAfterRollback as di, s3BucketArn as dn, PartialFailureError as do, repairMalformedOrphanRecordsForReadOnly as dr, ProviderRegistry as dt, getDefaultStateBucketName as ea, formatLockExpiry as ei, configStringRefusal as en, ConfigError as eo, refuseMalformedOrphans as er, collectDeclaredOutputNames as et, updatePartialReason as f, CFN_TEMPLATE_BODY_LIMIT as fa, orphansCarriedFrom as fi, s3BucketDomainName as fn, ProvisioningError as fo, repairMalformedOrphansForReadOnly as fr, wouldReturnToSdkProvider as ft, computeImplicitDeleteEdges as g, uploadCfnTemplate as ga, WorkGraph as gi, applyRoleArnIfSet as gn, StackTerminationProtectionError as go, repairMalformedResourcesForReadOnly as gr, isInterruptedWaitError as gt, IMPLICIT_DELETE_DEPENDENCIES as h, findLargeInlineResources as ha, stringifyValue as hi, s3BucketWebsiteUrl as hn, StackHasActiveImportsError as ho, repairMalformedResourcePropertiesForReadOnly as hr, interruptWatchListenerCount as ht, DeploymentEventsReader as i, resolveCaptureObservedState as ia, exportNamesCarriedFrom as ii, requireConfigObject as in, DynamicReferenceRegionAmbiguousError as io, refuseMalformedOutputsForDestroy as ir, secretBearing as it, isFinalSnapshotError as j, AssemblyReader as ja, buildDenyExternalAccessPolicy as ji, hasReadableResources as jn, markNonRetryable as jo, carriesSecretMask as jr, isWaitAbandonedError as jt, ccRoutedFinalSnapshotError as k, clientDefaultsFor as ka, validateAssetBucketName as ki, hasReadableOrphans as kn, isThrottlingError as ko, STATE_SOURCED_READBACK_RULES as kr, deleteIndeterminateGuards as kt, replayRollback as l, stateBucketExistenceConfirmed as la, importableOutputs as li, producerRegionsFromState as ln, MalformedProducerRecordRefusalError as lo, refuseMalformedResourcesForDestroy as lr, collectInlinePolicyNamesManagedBySiblings as lt, maskingRetryLogger as m, MIGRATE_TMP_PREFIX as ma, AssetPublisher as mi, s3BucketRegionalDomainName as mn, ResourceUpdateNotSupportedError as mo, repairMalformedResourceEntriesForReadOnly as mr, endCommandInterruptScope as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, resolveApp as na, DEFAULT_STATE_PREFIX as ni, replayWarn as nn, DependencyError as no, refuseMalformedOrphansForOrphan as nr, exportAliasCollisionScrubWarning as nt, planFailedOps as o, resolveStateBucketWithDefault as oa, hasReasonlessBaselineRefusal as oi, definedAttributes as on, LocalInvokeBuildError as oo, refuseMalformedResourceEntries as or, secretSafeKeyDisplay as ot, withResourceDeadline as p, CFN_TEMPLATE_URL_LIMIT as pa, shouldRetainResource as pi, s3BucketDualStackDomainName as pn, ResourceTimeoutError as po, repairMalformedOutputsForReadOnly as pr, beginCommandInterruptScope as pt, formatResourceLine as q, AwsClients as qa, warnFinchArgvExposure as qi, malformedResourcesWarning as qn, LockManager as qr, normalizeAwsTagsToCfn as qt, DeployEngine as r, resolveAutoAssetStorage as ra, describeRegionValueKind as ri, requireConfigArray as rn, DeployCancelledError as ro, refuseMalformedOutputs as rr, isExportAliasCollision as rt, planRollback as s, resolveStateBucketWithDefaultAndSource as sa, hasUnverifiableParameterRefusal as si, stringifyIfAssigned as sn, LocalStartServiceError as so, refuseMalformedResourceEntriesForOrphan as sr, getCurrentResourceSecrets as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, getLegacyStateBucketName as ta, CUSTOM_RESOURCE_RESPONSE_PREFIX as ti, readConfigString as tn, CrossAccountSecretRefusalError as to, refuseMalformedOrphansForDestroy as tr, collectPublishedOutputNames as tt, resolveReplacementOldType as u, warnDeprecatedNoPrefixCliFlag as ua, isReadableBag as ui, withSharedDrainBudget as un, NestedStackChildDirectDestroyError as uo, refuseMalformedState as ur, clearOnUpdateRemoval as ut, isSecretBearingReferenceString as v, injectiveKey as va, createAssetRedirectResolver as vi, applyCallerIdentityCredentials as vn, SynthesisError as vo, safeStackName as vr, CloudControlProvider as vt, reachableDivergentParameters as w, PARTITION_TABLE as wa, ensureAssetStorage as wi, STATE_RESOURCES_MALFORMED as wn, pasteableCommand as wo, withRetry as wr, packCompositeId as wt, bindingSkippedOutputs as x, producerRecordKey as xa, AssetModeResolver as xi, DiffCalculator as xn, normalizeAwsError as xo, findActionableSilentDrops as xr, compositeIdFormatMessage as xt, keptWholeReasonText as y, injectiveKeyPrefix as ya, loadPublishableAssetManifest as yi, callerEnvCredentials as yn, formatError as yo, unpreviewableOrphanRecords as yr, slowCcOperationTimeoutMs as yt, isStatefulRecreateTargetForReplace as z, ownValue as za, formatDockerLoginError as zi, malformedOrphanRowsKeptWarning as zn, maskSecretsInError as zr, getAccountInfo as zt };
53704
+ //# sourceMappingURL=deploy-engine-DIZztJy5.js.map