cdk-local 0.61.0 → 0.61.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-map-resolver-BIGVKpx5.js","names":["obj","pickStack","notFoundError","extractImageUri","resolveAssetCodePath","pickStack","notFoundError","inner","shortJson","readApiCdkPath","resolveLambdaArnShared","pickStage","pickStringArray","obj","execFileAsync","e","e","delay","isTransientNetworkError","join","delay","isTransientNetworkError","isTransientNetworkError","delay","safeJsonParse","safeJsonParse","delay","outer","resolveLambdaArnShared","MOCK_ACCOUNT_ID","MOCK_DOMAIN_NAME","MOCK_API_ID","formatRequestTime","websocketBody.bufferToBody","safeDecode","fn","splitRawUrl","createHttpsServer","createServer"],"sources":["../src/cli/options.ts","../src/utils/role-arn.ts","../src/utils/error-handler.ts","../src/cli/cdk-path.ts","../src/cli/stack-matcher.ts","../src/local/intrinsic-image.ts","../src/utils/stringify.ts","../src/local/lambda-resolver.ts","../src/local/agentcore-resolver.ts","../src/local/intrinsic-lambda-arn.ts","../src/local/intrinsic-utils.ts","../src/local/httpv2-service-integration.ts","../src/local/route-discovery.ts","../src/local/websocket-route-discovery.ts","../src/local/target-lister.ts","../src/local/target-picker.ts","../src/synthesis/cdkl-io-host.ts","../src/synthesis/assembly-reader.ts","../src/synthesis/synthesizer.ts","../src/cli/config-loader.ts","../src/local/ssm-parameter-resolver.ts","../src/local/cfn-local-state-provider.ts","../src/utils/profile-region.ts","../src/cli/commands/local-state-source.ts","../src/local/layer-arn-materializer.ts","../src/local/env-resolver.ts","../src/local/state-resolver.ts","../src/local/ecs-task-resolver.ts","../src/local/runtime-image.ts","../src/local/docker-runner.ts","../src/assets/docker-build.ts","../src/local/ecr-puller.ts","../src/local/docker-image-builder.ts","../src/local/rie-client.ts","../src/assets/asset-manifest-loader.ts","../src/utils/single-flight.ts","../src/cli/commands/local-profile-credentials-file.ts","../src/local/agentcore-code-build.ts","../src/local/agentcore-s3-bundle.ts","../src/local/agentcore-client.ts","../src/local/agentcore-sigv4-sign.ts","../src/local/agentcore-mcp-client.ts","../src/local/agentcore-a2a-client.ts","../src/local/agentcore-ws-client.ts","../src/local/cors-handler.ts","../src/local/authorizer-resolver.ts","../src/local/cognito-jwt.ts","../src/utils/glob-match.ts","../src/local/websocket-event.ts","../src/local/websocket-mgmt-api.ts","../src/local/websocket-body.ts","../src/local/websocket-server.ts","../src/local/docker-version.ts","../src/local/vtl-engine.ts","../src/local/integration-response-selector.ts","../src/local/rest-v1-integrations.ts","../src/local/container-pool.ts","../src/local/api-gateway-event.ts","../src/local/api-gateway-response.ts","../src/local/route-matcher.ts","../src/local/authorizer-context.ts","../src/local/lambda-authorizer.ts","../src/local/sigv4-verify.ts","../src/local/parameter-mapping.ts","../src/local/http-server.ts","../src/local/api-server-grouping.ts","../src/local/stage-resolver.ts","../src/local/file-watcher.ts","../src/local/authorizer-cache.ts","../src/cli/commands/local-start-api.ts","../src/local/docker-inspect.ts","../src/local/cloud-map-registry.ts","../src/local/cloud-map-resolver.ts"],"sourcesContent":["import { Option } from 'commander';\nimport { getEmbedConfig } from '../local/embed-config.js';\n\n/**\n * Parse context key=value pairs from CLI arguments into a Record.\n */\nexport function parseContextOptions(contextArgs?: string[]): Record<string, string> {\n const context: Record<string, string> = {};\n if (contextArgs) {\n for (const arg of contextArgs) {\n const eqIndex = arg.indexOf('=');\n if (eqIndex > 0) {\n context[arg.substring(0, eqIndex)] = arg.substring(eqIndex + 1);\n }\n }\n }\n return context;\n}\n\n/**\n * Options shared across every cdk-local command. Built per-call (not a\n * module-level const) so the `--role-arn` env-var hint reflects the active\n * embed config, which the command factory installs before calling this.\n *\n * `--region` is intentionally NOT in `commonOptions` — local commands\n * pick the region from `AWS_REGION` / profile / synthesized stack env.\n * The deprecated flag below remains for muscle-memory compatibility.\n */\nexport function commonOptions(): Option[] {\n const { envPrefix } = getEmbedConfig();\n return [\n new Option('--verbose', 'Enable verbose logging').default(false),\n new Option('--profile <profile>', 'AWS profile'),\n new Option(\n '--role-arn <arn>',\n `IAM role ARN to assume for AWS API calls (env: ${envPrefix}_ROLE_ARN)`\n ),\n new Option(\n '-y, --yes',\n 'Automatically answer interactive prompts with the recommended response'\n ).default(false),\n ];\n}\n\n/**\n * Deprecated `--region` option attached to every command.\n *\n * Kept (rather than fully removed) so that scripts or muscle memory\n * passing `--region` do not break. The value is parsed but ignored;\n * the SDK picks the region from `AWS_REGION` / profile / synthesized\n * stack env.\n */\nexport const deprecatedRegionOption = new Option(\n '--region <region>',\n '[deprecated] No effect on this command; use AWS_REGION or your AWS profile'\n).hideHelp();\n\n/**\n * Emit a one-shot stderr warning when a command receives `--region`.\n */\nexport function warnIfDeprecatedRegion(options: { region?: string }): void {\n if (options.region !== undefined) {\n process.stderr.write(\n 'Warning: --region is deprecated for this command and has no effect. ' +\n 'Use the AWS_REGION environment variable or your AWS profile to override the SDK default region.\\n'\n );\n }\n}\n\n/**\n * App options. Built per-call (not a module-level const) so the `--app`\n * env-var hint reflects the active embed config.\n *\n * `--app` is optional: falls back to `${envPrefix}_APP` env var, then\n * `cdk.json` `app` field. Accepts either a shell command (e.g.\n * `\"node app.ts\"`) or a path to a pre-synthesized cloud assembly directory\n * (e.g. `\"cdk.out\"`).\n */\nexport function appOptions(): Option[] {\n const { envPrefix } = getEmbedConfig();\n return [\n new Option(\n '-a, --app <command>',\n `CDK app command (e.g., \"node app.ts\") or path to a pre-synthesized cloud assembly directory. Falls back to cdk.json or ${envPrefix}_APP env`\n ),\n new Option('--output <path>', 'Output directory for synthesis').default('cdk.out'),\n ];\n}\n\n/**\n * Context options.\n */\nexport const contextOptions = [\n new Option(\n '-c, --context <key=value...>',\n 'Set context values (can be specified multiple times)'\n ),\n];\n\n/**\n * Per-Lambda + global `--assume-role` parser used by `cdkl start-api`.\n * Each invocation is either a bare ARN (sets / overwrites the global\n * default) or `<LogicalId>=<arn>` (per-Lambda override). Per-Lambda\n * always wins over global; global is the fallback when no per-Lambda\n * entry exists.\n */\nexport interface AssumeRoleOption {\n /** Global ARN — last bare-arn token wins. */\n globalArn?: string;\n /** Per-Lambda override map (`LogicalId` -> ARN). */\n perLambda: Record<string, string>;\n}\n\nconst IAM_ROLE_ARN_REGEX = /^arn:[^:]+:iam::\\d+:role\\//;\n\nexport function parseAssumeRoleToken(\n raw: string,\n previous: AssumeRoleOption | undefined\n): AssumeRoleOption {\n const acc: AssumeRoleOption = previous ?? { perLambda: {} };\n if (!acc.perLambda) acc.perLambda = {};\n\n const eqIndex = raw.indexOf('=');\n if (eqIndex === -1) {\n if (!IAM_ROLE_ARN_REGEX.test(raw)) {\n throw new Error(\n `Invalid --assume-role value \"${raw}\": expected an IAM role ARN like arn:aws:iam::123456789012:role/MyRole, or LogicalId=<arn>.`\n );\n }\n acc.globalArn = raw;\n return acc;\n }\n\n const logicalId = raw.substring(0, eqIndex).trim();\n const arn = raw.substring(eqIndex + 1).trim();\n if (!/^[A-Za-z][A-Za-z0-9]*$/.test(logicalId)) {\n throw new Error(\n `Invalid --assume-role value \"${raw}\": left-hand side \"${logicalId}\" must be a CloudFormation logical ID (alphanumeric, leading letter).`\n );\n }\n if (!IAM_ROLE_ARN_REGEX.test(arn)) {\n throw new Error(\n `Invalid --assume-role value \"${raw}\": right-hand side \"${arn}\" must be an IAM role ARN like arn:aws:iam::123456789012:role/MyRole.`\n );\n }\n acc.perLambda[logicalId] = arn;\n return acc;\n}\n\n/**\n * Resolve the effective IAM role ARN for a given Lambda. Per-Lambda\n * override wins; otherwise the global default; otherwise `undefined`\n * (no role to assume — pass developer creds through).\n */\nexport function effectiveAssumeRoleArn(\n logicalId: string,\n opt: AssumeRoleOption | undefined\n): string | undefined {\n if (!opt) return undefined;\n return opt.perLambda?.[logicalId] ?? opt.globalArn;\n}\n","import { STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts';\nimport { getLogger } from './logger.js';\nimport { getEmbedConfig } from '../local/embed-config.js';\n\n/**\n * Resolve the role-arn argument (CLI flag or `CDKL_ROLE_ARN` env var) and,\n * when set, assume the role and write the resulting temporary credentials\n * into `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`\n * for the rest of the process.\n *\n * Why env vars, not threaded credentials: cdk-local constructs several\n * independent AWS clients (Lambda invoke, ECR pull, etc.). Threading a\n * `credentials` object through every site is high churn for an opt-in\n * flag. AWS SDK v3 reads the standard `AWS_*` env vars at the top of its\n * default credentials chain, so writing into them once at the command's\n * entry makes every later `new XxxClient()` pick up the assumed-role\n * credentials automatically without touching the client construction sites.\n *\n * Default session duration is 1 hour.\n */\nexport async function applyRoleArnIfSet(opts: {\n roleArn: string | undefined;\n region: string | undefined;\n}): Promise<void> {\n const roleArn = opts.roleArn || process.env[`${getEmbedConfig().envPrefix}_ROLE_ARN`];\n if (!roleArn) return;\n\n const logger = getLogger().child('role-arn');\n logger.debug(`Assuming role ${roleArn}...`);\n\n const sts = new STSClient({ ...(opts.region && { region: opts.region }) });\n try {\n const response = await sts.send(\n new AssumeRoleCommand({\n RoleArn: roleArn,\n RoleSessionName: `${getEmbedConfig().binaryName}-${Date.now()}`,\n DurationSeconds: 3600,\n })\n );\n if (!response.Credentials) {\n throw new Error(`AssumeRole returned no credentials for role ${roleArn}`);\n }\n const { AccessKeyId, SecretAccessKey, SessionToken, Expiration } = response.Credentials;\n if (!AccessKeyId || !SecretAccessKey || !SessionToken) {\n throw new Error(`AssumeRole response missing credentials fields for role ${roleArn}`);\n }\n process.env['AWS_ACCESS_KEY_ID'] = AccessKeyId;\n process.env['AWS_SECRET_ACCESS_KEY'] = SecretAccessKey;\n process.env['AWS_SESSION_TOKEN'] = SessionToken;\n logger.info(\n `Assumed role ${roleArn} (session expires ${Expiration?.toISOString() ?? 'unknown'})`\n );\n } finally {\n sts.destroy();\n }\n}\n","import { getLogger } from './logger.js';\n\n/**\n * Base error class for cdk-local\n */\nexport class CdkLocalError extends Error {\n public readonly code: string;\n public readonly cause: Error | undefined;\n\n constructor(message: string, code: string, cause?: Error) {\n super(message);\n this.code = code;\n this.cause = cause;\n this.name = 'CdkLocalError';\n Object.setPrototypeOf(this, CdkLocalError.prototype);\n }\n}\n\n/**\n * Local-invoke `docker build` failures.\n *\n * Surfaces the stderr captured from `docker build` so the user can\n * re-run the same command directly to debug Dockerfile syntax errors\n * or missing build context. Used by `src/local/docker-image-builder.ts`\n * for container Lambdas. Kept distinct from a generic asset/build error\n * so `cdkl invoke` failures don't show up under an unrelated error class.\n */\nexport class LocalInvokeBuildError extends CdkLocalError {\n constructor(message: string, cause?: Error) {\n super(message, 'LOCAL_INVOKE_BUILD_ERROR', cause);\n this.name = 'LocalInvokeBuildError';\n Object.setPrototypeOf(this, LocalInvokeBuildError.prototype);\n }\n}\n\n/**\n * Signals that `cdkl start-api`'s route discovery hit an unsupported\n * shape — non-AWS_PROXY integration, ApiGwV2 service integration\n * (`IntegrationSubtype` set), WebSocket protocol, Lambda::Url with an\n * unrecognized `AuthType` (anything other than `'NONE'` / `'AWS_IAM'`),\n * or an unsupported intrinsic function in `IntegrationUri`. (Lambda::Url\n * with `InvokeMode: RESPONSE_STREAM` is a normal route dispatched via\n * the streaming protocol. Lambda::Url with `AuthType: 'AWS_IAM'`\n * is a normal route verified through the SigV4 pipeline.)\n *\n * The message names every offending route. Hard-error at discovery so\n * the server never starts in a half-working state.\n */\nexport class RouteDiscoveryError extends CdkLocalError {\n constructor(message: string, cause?: Error) {\n super(message, 'ROUTE_DISCOVERY_ERROR', cause);\n this.name = 'RouteDiscoveryError';\n Object.setPrototypeOf(this, RouteDiscoveryError.prototype);\n }\n}\n\n/**\n * Signals a `cdkl start-service` orchestration failure\n * (`AWS::ECS::Service` emulator). The service runner has its own\n * lifecycle (long-running replica pool, restart-on-exit), so a failure\n * inside it carries different operator semantics than a one-shot task\n * failure.\n */\nexport class LocalStartServiceError extends CdkLocalError {\n constructor(message: string, cause?: Error) {\n super(message, 'LOCAL_START_SERVICE_ERROR', cause);\n this.name = 'LocalStartServiceError';\n Object.setPrototypeOf(this, LocalStartServiceError.prototype);\n }\n}\n\n/**\n * The user aborted an interactive target picker (Ctrl+C / Esc). Exits\n * 130 (the conventional SIGINT code) and suppresses the error line —\n * cancelling a prompt is a normal user action, not a failure.\n */\nexport class TargetSelectionCancelledError extends CdkLocalError {\n public readonly silent = true;\n public readonly exitCode = 130;\n constructor() {\n super('Target selection cancelled.', 'TARGET_SELECTION_CANCELLED');\n this.name = 'TargetSelectionCancelledError';\n Object.setPrototypeOf(this, TargetSelectionCancelledError.prototype);\n }\n}\n\n/**\n * `-i/--interactive` was passed (or a required target was omitted) but\n * the session has no TTY, so no prompt can be shown.\n */\nexport class InteractiveTtyRequiredError extends CdkLocalError {\n constructor(message: string) {\n super(message, 'INTERACTIVE_TTY_REQUIRED');\n this.name = 'InteractiveTtyRequiredError';\n Object.setPrototypeOf(this, InteractiveTtyRequiredError.prototype);\n }\n}\n\n/**\n * Check if error is a cdk-local error\n */\nexport function isCdkLocalError(error: unknown): error is CdkLocalError {\n return error instanceof CdkLocalError;\n}\n\n/**\n * Format error for display\n */\nexport function formatError(error: unknown): string {\n if (isCdkLocalError(error)) {\n let message = `${error.name}: ${error.message}`;\n if (error.cause) {\n message += `\\nCaused by: ${error.cause.message}`;\n }\n return message;\n }\n\n if (error instanceof Error) {\n return `${error.name}: ${error.message}`;\n }\n\n return String(error);\n}\n\n/**\n * Global error handler\n *\n * Default exit code is 1 (general error). A {@link CdkLocalError}\n * subclass may override it by declaring a custom `exitCode` field so\n * callers can distinguish \"command crashed / unauthorized / bad\n * arguments\" from \"command completed but some resources are still in an\n * error state, re-run to clean up\".\n *\n * A {@link CdkLocalError} subclass may set `silent = true` to suppress\n * the default `logger.error` line — used when the command has already\n * printed a richer report and only needs the exit code.\n */\nexport function handleError(error: unknown): never {\n const logger = getLogger();\n const silent =\n error instanceof CdkLocalError && (error as CdkLocalError & { silent?: boolean }).silent;\n if (!silent) {\n logger.error(formatError(error));\n }\n\n if (error instanceof Error && error.stack) {\n logger.debug('Stack trace:', error.stack);\n }\n\n // Honor any CdkLocalError subclass that declares a custom `exitCode`\n // field. Falling back to 1 covers `CdkLocalError` subclasses with no\n // override and every non-cdk-local error.\n const customExitCode =\n error instanceof CdkLocalError\n ? (error as CdkLocalError & { exitCode?: number }).exitCode\n : undefined;\n const exitCode = typeof customExitCode === 'number' ? customExitCode : 1;\n process.exit(exitCode);\n}\n\n/**\n * Wrap async function with error handling\n *\n * Note: Uses `any[]` for args to support Commander.js action handlers\n * which can have various parameter types\n */\nexport function withErrorHandling<Args extends unknown[], Return extends Promise<void> | void>(\n fn: (...args: Args) => Return\n): (...args: Args) => Promise<void> {\n return async (...args: Args): Promise<void> => {\n try {\n await fn(...args);\n } catch (error) {\n handleError(error);\n }\n };\n}\n\n/**\n * Context passed to {@link normalizeAwsError} so the rewritten message can\n * name the bucket/operation that produced the synthetic SDK error.\n */\nexport interface NormalizeAwsErrorContext {\n bucket?: string;\n operation?: string;\n}\n\n/**\n * Convert AWS SDK v3's synthetic `Unknown` / `UnknownError` exception into\n * an actionable `Error` keyed off `$metadata.httpStatusCode`.\n *\n * Background — why this helper exists:\n * AWS SDK v3 produces a synthetic `name: 'Unknown'`, `message:\n * 'UnknownError'` exception when the protocol parser hits a HEAD response\n * with an empty body. The most common trigger is `HeadBucket` against a\n * bucket in a different region than the client (S3 returns 301\n * PermanentRedirect with `x-amz-bucket-region` set, but the redirect\n * middleware doesn't recover from the empty body). Surfacing the literal\n * string `UnknownError` to users is uninformative.\n *\n * Behavior:\n * - Non-AWS-SDK errors (anything where `name` is not `Unknown` and\n * `message` is not `UnknownError`) pass through unchanged.\n * - AWS SDK Unknown errors are mapped by HTTP status:\n * - 301 → `Bucket '<name>' is in a different region…` (auto-resolved\n * elsewhere; if this surfaces, it's a bug worth reporting).\n * - 403 → `Access denied to bucket '<name>'.`\n * - 404 → `Bucket '<name>' does not exist.`\n * - other / unknown → `S3 error during <operation> on '<bucket>' (HTTP\n * <status>).`\n */\nexport function normalizeAwsError(err: unknown, context: NormalizeAwsErrorContext = {}): Error {\n if (!(err instanceof Error)) {\n return new Error(String(err));\n }\n\n // Detect the AWS SDK v3 \"Unknown\" synthetic exception. Other errors pass\n // through unchanged so we don't accidentally rewrite a legitimate AWS\n // error message.\n const isUnknown = err.name === 'Unknown' || err.message === 'UnknownError';\n if (!isUnknown) return err;\n\n const meta = (err as { $metadata?: { httpStatusCode?: number } }).$metadata;\n const status = meta?.httpStatusCode;\n const bucket = context.bucket ?? '<unknown bucket>';\n const operation = context.operation ?? 'operation';\n\n switch (status) {\n case 301: {\n // Try to surface the bucket's actual region from the response header\n // when the SDK exposes it. Header keys are lowercased by the SDK.\n const responseHeaders = (err as { $response?: { headers?: Record<string, string> } })\n .$response?.headers;\n const region =\n responseHeaders?.['x-amz-bucket-region'] ?? responseHeaders?.['X-Amz-Bucket-Region'];\n const where = region ? ` (in ${region})` : '';\n return new Error(\n `Bucket '${bucket}'${where} is in a different region than the client. ` +\n `cdk-local resolves this automatically; if you see this message, please report it.`\n );\n }\n case 403:\n return new Error(\n `Access denied to bucket '${bucket}'. Verify credentials and bucket policy.`\n );\n case 404:\n return new Error(`Bucket '${bucket}' does not exist.`);\n default: {\n const statusStr = status !== undefined ? `HTTP ${status}` : 'unknown HTTP status';\n return new Error(\n `S3 error during ${operation} on '${bucket}' (${statusStr}). ` +\n `See CloudTrail for details.`\n );\n }\n }\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\n\n/**\n * Read the `aws:cdk:path` value that CDK encodes into every resource's\n * `Metadata`. Returns the empty string when not present so callers don't\n * have to special-case `undefined`.\n */\nexport function readCdkPath(resource: TemplateResource): string {\n const meta = resource.Metadata;\n if (!meta) return '';\n const v = (meta as { 'aws:cdk:path'?: unknown })['aws:cdk:path'];\n return typeof v === 'string' ? v : '';\n}\n\n/**\n * Same lookup as `readCdkPath`, but returns `undefined` instead of an\n * empty string when no `aws:cdk:path` metadata is present.\n *\n * Use this variant when the caller passes the value into APIs whose\n * contract distinguishes \"no path known\" from \"empty path\". For example,\n * `resolveEnvVars(logicalId, displayPath, ...)` short-circuits the\n * display-path lookup when `displayPath` is `undefined`, but an empty\n * string `''` would still hit the loop and could spuriously match a\n * malformed override key.\n */\nexport function readCdkPathOrUndefined(resource: TemplateResource): string | undefined {\n const path = readCdkPath(resource);\n return path === '' ? undefined : path;\n}\n\n/**\n * Build a `Map<cdkPath, logicalId>` from a synthesized template.\n *\n * Translates user-supplied construct paths (which mirror the upstream\n * `cdk` construct-path UX) back to the logical IDs that the rest of the\n * pipeline (state, dependency analysis, provider lookup) is keyed on.\n *\n * `AWS::CDK::Metadata` resources are excluded — the synthesized\n * `<Stack>/CDKMetadata/Default` sentinel exists in every stack but is\n * never user-managed, so listing it as an \"available path\" in the\n * not-found error is just noise and orphaning it is meaningless.\n *\n * Resources without a `aws:cdk:path` metadata entry are silently skipped\n * for the same reason — they cannot be addressed by construct path.\n *\n * In practice each path maps to a single logical ID. If the same path\n * happens to appear twice (which would itself be a bug in the synthesized\n * template), the last entry wins — callers still surface a\n * clean \"path not found\" diff against the indexed map rather than\n * silently grabbing both.\n */\nexport function buildCdkPathIndex(template: CloudFormationTemplate): Map<string, string> {\n const index = new Map<string, string>();\n for (const [logicalId, resource] of Object.entries(template.Resources)) {\n if (resource.Type === 'AWS::CDK::Metadata') continue;\n const path = readCdkPath(resource);\n if (path) index.set(path, logicalId);\n }\n return index;\n}\n\n/**\n * Resolve a user-supplied construct path to every logical ID it covers.\n *\n * Mirrors `cdk orphan --unstable=orphan` (`packages/@aws-cdk/toolkit-lib/\n * lib/api/orphan/orphaner.ts` line 90 in aws-cdk-cli): users typically\n * pass an L2 path like `MyStack/MyConstruct/MyBucket` rather than the\n * synthesized L1 path `MyStack/MyConstruct/MyBucket/Resource`, and an L2\n * with multiple children (e.g. a CDK pattern that wraps several CFn\n * resources) should orphan all of them in one go.\n *\n * Match rule: a resource matches `input` when its `aws:cdk:path` is\n * exactly `input` OR starts with `${input}/`. The trailing slash matters\n * — without it `MyStack/MyBucket` would also match\n * `MyStack/MyBucketBackup/Resource`.\n */\nexport function resolveCdkPathToLogicalIds(\n input: string,\n index: Map<string, string>\n): { logicalId: string; cdkPath: string }[] {\n const seen = new Map<string, string>();\n const prefix = `${input}/`;\n for (const [path, logicalId] of index) {\n if (path === input || path.startsWith(prefix)) {\n if (!seen.has(logicalId)) seen.set(logicalId, path);\n }\n }\n return [...seen.entries()].map(([logicalId, cdkPath]) => ({ logicalId, cdkPath }));\n}\n","/**\n * Match stacks against user-supplied name patterns.\n *\n * Patterns are evaluated against two fields:\n *\n * - `stackName` — the physical CloudFormation stack name (e.g. `MyStage-MyStack`)\n * - `displayName` — the hierarchical CDK path (e.g. `MyStage/MyStack`); falls\n * back to `stackName` when the assembly does not carry one\n *\n * Routing is decided by whether the pattern contains `/`:\n *\n * - Pattern contains `/` → matched only against `displayName` (a `/` cannot\n * appear in a CloudFormation stack name, so this is unambiguous)\n * - Pattern contains no `/` → matched only against `stackName`\n *\n * Wildcards (`*`) are supported in either case. Results are de-duplicated by\n * `stackName`, so a pattern that incidentally matches the same stack via both\n * fields is returned only once.\n */\nexport interface StackLike {\n stackName: string;\n displayName?: string;\n}\n\nexport function matchStacks<T extends StackLike>(stacks: T[], patterns: string[]): T[] {\n if (patterns.length === 0) return [];\n\n const seen = new Set<string>();\n const result: T[] = [];\n\n for (const stack of stacks) {\n const matched = patterns.some((pattern) => stackMatchesPattern(stack, pattern));\n if (matched && !seen.has(stack.stackName)) {\n seen.add(stack.stackName);\n result.push(stack);\n }\n }\n\n return result;\n}\n\n/**\n * Render a stack for diagnostic messages. When `displayName` differs from the\n * physical name, both are shown so the user can see which forms are valid as\n * patterns (e.g. `MyStage-Api (MyStage/Api)`).\n */\nexport function describeStack(stack: StackLike): string {\n if (stack.displayName && stack.displayName !== stack.stackName) {\n return `${stack.stackName} (${stack.displayName})`;\n }\n return stack.stackName;\n}\n\nexport function stackMatchesPattern(stack: StackLike, pattern: string): boolean {\n const target = pattern.includes('/') ? (stack.displayName ?? stack.stackName) : stack.stackName;\n if (pattern.includes('*')) {\n const regex = new RegExp('^' + pattern.replace(/\\*/g, '.*') + '$');\n return regex.test(target);\n }\n return target === pattern;\n}\n","import type { ResourceState } from '../types/state.js';\nimport type { TemplateResource } from '../types/resource.js';\n\n/**\n * Shared resolver for CFn intrinsic-function shapes that show up in\n * container-image URI fields — both ECS `ContainerDefinition.Image`\n * (`cdkl run-task`) and Lambda `Code.ImageUri` (`cdk-local\n * invoke` container Lambdas). CDK 2.x synthesizes the same canonical\n * `Fn::Join` shape for `ContainerImage.fromEcrRepository(repo, tag)` and\n * `lambda.DockerImageCode.fromEcr(repo, { tagOrDigest })` — so both call\n * sites share the resolver.\n *\n * Originally introduced as a private helper inside `ecs-task-resolver.ts`\n * (PR #280 / issue #271) and extracted here when `lambda-resolver.ts`\n * needed the same shape (issue #286 Gap 2). The extraction keeps the two\n * call sites bit-identical so a future change in the canonical CDK shape\n * gets fixed once.\n *\n * Scope is intentionally narrow: the resolver only handles the subset of\n * intrinsics needed to reconstruct an ECR image URI. `Fn::If` /\n * `Fn::FindInMap` / etc. are out of scope — this is a minimal resolver\n * for image URIs, not a general-purpose deploy-time resolver.\n */\n\n/**\n * Substitution context for `tryResolveImageFnJoin` and `substituteImagePlaceholders`.\n *\n * Both blocks are optional: `pseudoParameters` covers Tier 1 (no state\n * needed — works against the developer's shell creds / region for\n * `${AWS::Region}` / `${AWS::AccountId}` / `${AWS::URLSuffix}` /\n * `${AWS::Partition}`); `stateResources` covers Tier 2 (`--from-state`\n * — substitutes same-stack ECR `Ref` / `Fn::GetAtt: [<Repo>, 'Arn']`\n * against the host state-recorded `physicalId` / `attributes`).\n *\n * The CLI command resolves both blocks lazily — STS is only invoked when\n * at least one image references the pseudo parameters — and passes the\n * resolved shape here. The resolver itself stays pure and synchronous.\n */\nexport interface ImageResolutionContext {\n /**\n * Resolved AWS pseudo parameters. When undefined for a given key, the\n * substitution is treated as missing and the value passes through\n * verbatim. Caller is expected to populate every key when it populates\n * any (we derive partition / URL suffix from region in the CLI layer).\n */\n pseudoParameters?: {\n accountId?: string;\n region?: string;\n partition?: string;\n urlSuffix?: string;\n };\n /**\n * `state.resources` from the host's S3 state record for the target stack,\n * loaded by the CLI command before resolution when `--from-state` is\n * passed. Used to substitute `${<LogicalId>}` against an\n * `AWS::ECR::Repository` and the `Fn::GetAtt` `Arn` / `RepositoryUri`\n * shapes. Undefined when `--from-state` is not in effect.\n */\n stateResources?: Record<string, ResourceState>;\n /**\n * Resolved SSM-backed template `Parameters`\n * (`AWS::SSM::Parameter::Value<String>`), keyed by parameter logical ID.\n * Fed into `state-resolver.ts`'s `SubstitutionContext.parameters` so a\n * `Ref` to such a parameter in a container `Environment` / `Secrets`\n * entry resolves to its SSM value instead of being warn-and-dropped\n * (issue #94). The CLI resolves these out-of-band via SSM Parameter\n * Store under `--from-cfn-stack`. Undefined when the stack declares no\n * SSM-backed parameters / no state source is in effect.\n */\n stateParameters?: Record<string, string>;\n /**\n * Logical IDs of {@link stateParameters} entries whose SSM `Type` is\n * `SecureString` (decrypted). Threaded into\n * `SubstitutionContext.sensitiveParameters` so the consuming container\n * env keys are routed off the `docker run` argv (issue #99). Undefined\n * when no SecureString parameter was resolved.\n */\n stateSensitiveParameters?: readonly string[];\n}\n\n/**\n * Derive the AWS pseudo parameters that are trivially knowable from the\n * deploy region alone, without any STS call or state load.\n * `urlSuffix` and `partition` follow the canonical AWS partition rules:\n *\n * - region prefix `cn-*` → partition `aws-cn`, urlSuffix `amazonaws.com.cn`\n * - region prefix `us-gov-*` → partition `aws-us-gov`, urlSuffix `amazonaws.com`\n * - region prefix `us-iso-*` → partition `aws-iso`, urlSuffix `c2s.ic.gov`\n * - region prefix `us-isob-*` → partition `aws-iso-b`, urlSuffix `sc2s.sgov.gov`\n * - everything else (`us-east-1` / `eu-west-2` / `ap-northeast-1` / etc.)\n * → partition `aws`, urlSuffix `amazonaws.com`\n *\n * `accountId` is optional pass-through (caller decides whether to populate\n * it). The bootstrap-ECR URI shape that `lambda.DockerImageCode.fromImageAsset`\n * synthesizes carries account-id + region as literal strings in the template,\n * so only `urlSuffix` / `partition` / `region` are required to resolve it\n * (issue #637).\n *\n * Returns `undefined` when `region` is undefined / empty so the caller can\n * fall through cleanly. The shape mirrors `ImageResolutionContext.pseudoParameters`\n * so the result drops straight into a context literal.\n */\nexport function derivePseudoParametersFromRegion(\n region: string | undefined,\n accountId?: string\n): { accountId?: string; region: string; partition: string; urlSuffix: string } | undefined {\n if (!region || typeof region !== 'string' || region.length === 0) return undefined;\n let partition: string;\n let urlSuffix: string;\n if (region.startsWith('cn-')) {\n partition = 'aws-cn';\n urlSuffix = 'amazonaws.com.cn';\n } else if (region.startsWith('us-gov-')) {\n partition = 'aws-us-gov';\n urlSuffix = 'amazonaws.com';\n } else if (region.startsWith('us-isob-')) {\n partition = 'aws-iso-b';\n urlSuffix = 'sc2s.sgov.gov';\n } else if (region.startsWith('us-iso-')) {\n partition = 'aws-iso';\n urlSuffix = 'c2s.ic.gov';\n } else {\n partition = 'aws';\n urlSuffix = 'amazonaws.com';\n }\n return {\n ...(accountId !== undefined && { accountId }),\n region,\n partition,\n urlSuffix,\n };\n}\n\n/**\n * Outcome of attempting to resolve a `Fn::Join`-shaped image URI against\n * the substitution context. Discriminated so the caller can route each\n * case to the right error / classification path.\n */\nexport type FnJoinResolveOutcome =\n | { kind: 'not-applicable' }\n | { kind: 'resolved'; uri: string }\n | { kind: 'needs-state'; repoLogicalId: string }\n | { kind: 'unsupported-join'; reason: string };\n\n/**\n * Resolve the canonical CDK 2.x `Fn::Join` shape emitted by\n * `ContainerImage.fromEcrRepository(repo, tag)` (ECS) and\n * `lambda.DockerImageCode.fromEcr(repo, { tagOrDigest })` (Lambda\n * container).\n *\n * The shape is a `Fn::Join` with delimiter `\"\"` whose elements include\n * nested `Fn::Select` / `Fn::Split` over an `Fn::GetAtt: [<Repo>, 'Arn']`\n * plus a `Ref` to the same `AWS::ECR::Repository` and a\n * `Ref: AWS::URLSuffix`. For SAME-STACK references the account-id +\n * region only exist in the host's S3 state (recorded at deploy time on the\n * Repository's `Arn` attribute), so the resolver inherently requires\n * `--from-state` (Tier 2) for that variant. For IMPORTED repositories\n * the URI components are flat strings + `Ref: AWS::URLSuffix` and\n * resolve cleanly without state (Tier 1).\n *\n * Returns `not-applicable` when `raw` isn't an `Fn::Join` (the caller\n * falls through to its existing `Fn::Sub` / flat-string handling).\n * Returns `needs-state` when the `Fn::Join` references a same-stack ECR\n * Repository but no state was supplied (the caller surfaces a\n * `--from-state` hint). Returns `unsupported-join` when the join shape\n * doesn't fit the canonical CDK 2.x pattern (e.g. delimiter != \"\",\n * non-recognized nested intrinsic) so the caller can route to a precise\n * error.\n */\nexport function tryResolveImageFnJoin(\n raw: unknown,\n resources: Record<string, TemplateResource>,\n context: ImageResolutionContext | undefined\n): FnJoinResolveOutcome {\n if (!raw || typeof raw !== 'object') return { kind: 'not-applicable' };\n const obj = raw as Record<string, unknown>;\n const arg = obj['Fn::Join'];\n if (arg === undefined) return { kind: 'not-applicable' };\n\n if (!Array.isArray(arg) || arg.length !== 2 || !Array.isArray(arg[1])) {\n return { kind: 'unsupported-join', reason: 'Fn::Join must be [delimiter, [elements]]' };\n }\n const [delimiter, elements] = arg as [unknown, unknown[]];\n if (typeof delimiter !== 'string') {\n return {\n kind: 'unsupported-join',\n reason: `Fn::Join delimiter must be a string, got ${typeof delimiter}`,\n };\n }\n\n // Find a same-stack ECR::Repository referenced by either a `Ref` or\n // `Fn::GetAtt` somewhere in the element tree. The presence of such a\n // reference is the load-bearing signal that this Fn::Join is an ECR\n // image URI (rather than an unrelated Join that happens to be the\n // Image field).\n const repoLogicalId = findEcrRepositoryRefInTree(elements, resources);\n\n const stateResources = context?.stateResources;\n if (repoLogicalId && !stateResources) {\n return { kind: 'needs-state', repoLogicalId };\n }\n\n // Walk every element through the generic intrinsic resolver. Any\n // unresolvable element aborts with `unsupported-join`.\n const parts: string[] = [];\n for (const element of elements) {\n const r = resolveImageIntrinsic(element, resources, context);\n if (r === undefined) {\n // No ECR Repository reference AND we could not produce a string —\n // this isn't a canonical CDK 2.x ECR Fn::Join. Surface\n // `not-applicable` so the caller falls back to its existing\n // flat-string / Fn::Sub path.\n if (!repoLogicalId) return { kind: 'not-applicable' };\n return {\n kind: 'unsupported-join',\n reason: 'one or more Fn::Join elements could not be resolved',\n };\n }\n parts.push(r);\n }\n\n return { kind: 'resolved', uri: parts.join(delimiter) };\n}\n\n/**\n * Walk a tree of intrinsic nodes and return the logical ID of the first\n * `AWS::ECR::Repository` referenced via `Ref` or `Fn::GetAtt`. Used to\n * detect whether a `Fn::Join` image shape is an ECR image URI (and so\n * needs Tier 2 / `--from-state` resolution).\n */\nfunction findEcrRepositoryRefInTree(\n node: unknown,\n resources: Record<string, TemplateResource>\n): string | undefined {\n if (node === null || node === undefined) return undefined;\n if (typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') {\n return undefined;\n }\n if (Array.isArray(node)) {\n for (const item of node) {\n const hit = findEcrRepositoryRefInTree(item, resources);\n if (hit) return hit;\n }\n return undefined;\n }\n if (typeof node !== 'object') return undefined;\n const obj = node as Record<string, unknown>;\n\n if (typeof obj['Ref'] === 'string') {\n const target = obj['Ref'];\n if (resources[target]?.Type === 'AWS::ECR::Repository') return target;\n return undefined;\n }\n\n const getAtt = obj['Fn::GetAtt'];\n if (getAtt !== undefined) {\n let lid: string | undefined;\n if (Array.isArray(getAtt) && typeof getAtt[0] === 'string') lid = getAtt[0];\n else if (typeof getAtt === 'string') lid = getAtt.split('.')[0];\n if (lid && resources[lid]?.Type === 'AWS::ECR::Repository') return lid;\n return undefined;\n }\n\n for (const value of Object.values(obj)) {\n const hit = findEcrRepositoryRefInTree(value, resources);\n if (hit) return hit;\n }\n return undefined;\n}\n\n/**\n * Generic recursive resolver for the intrinsic-function subset needed to\n * construct an ECR image URI from a `Fn::Join` tree. Handles:\n *\n * - literal strings / numbers / booleans (returned as their string form)\n * - `Ref: AWS::URLSuffix` / `AWS::Partition` / `AWS::Region` /\n * `AWS::AccountId` against `context.pseudoParameters`\n * - `Ref: <ECRRepoLogicalId>` against `context.stateResources` →\n * `physicalId`\n * - `Fn::GetAtt: [<ECRRepoLogicalId>, 'Arn'|'RepositoryUri']` against\n * `context.stateResources.attributes`\n * - `Fn::Split: [delimiter, str]` (where `str` resolves to a string)\n * - `Fn::Select: [index, list]` (where `list` resolves to an array)\n * - `Fn::Join: [delimiter, [elements]]` (recursive — each element\n * resolved via this function)\n * - `Fn::Sub: <template>` (string-replace via `substituteImagePlaceholders`)\n *\n * Returns `undefined` when any sub-resolution fails so the caller can\n * route the outer Fn::Join to `unsupported-join`. Deliberately tight\n * scope — `Fn::If` / `Fn::FindInMap` / etc. are out of scope here.\n */\nfunction resolveImageIntrinsic(\n node: unknown,\n resources: Record<string, TemplateResource>,\n context: ImageResolutionContext | undefined\n): string | undefined {\n const v = resolveImageIntrinsicAny(node, resources, context);\n if (typeof v === 'string') return v;\n if (typeof v === 'number' || typeof v === 'boolean') return String(v);\n return undefined;\n}\n\n/**\n * Same resolver as `resolveImageIntrinsic` but returns the raw resolved\n * value (string / number / boolean / array of strings). Used by\n * `Fn::Select` over a `Fn::Split` (which produces a string[]).\n */\nfunction resolveImageIntrinsicAny(\n node: unknown,\n resources: Record<string, TemplateResource>,\n context: ImageResolutionContext | undefined\n): string | number | boolean | string[] | undefined {\n if (node === null || node === undefined) return undefined;\n if (typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') {\n return node;\n }\n if (Array.isArray(node)) {\n // A bare array isn't a valid intrinsic at this layer.\n return undefined;\n }\n if (typeof node !== 'object') return undefined;\n const obj = node as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length !== 1) return undefined;\n const intrinsic = keys[0]!;\n const arg = obj[intrinsic];\n\n if (intrinsic === 'Ref') {\n if (typeof arg !== 'string') return undefined;\n if (arg.startsWith('AWS::')) {\n const p = context?.pseudoParameters;\n if (!p) return undefined;\n if (arg === 'AWS::URLSuffix') return p.urlSuffix;\n if (arg === 'AWS::Partition') return p.partition;\n if (arg === 'AWS::Region') return p.region;\n if (arg === 'AWS::AccountId') return p.accountId;\n return undefined;\n }\n const refResource = resources[arg];\n if (refResource?.Type !== 'AWS::ECR::Repository') return undefined;\n const stateEntry = context?.stateResources?.[arg];\n if (!stateEntry) return undefined;\n return stateEntry.physicalId;\n }\n\n if (intrinsic === 'Fn::GetAtt') {\n let logicalId: string | undefined;\n let attr: string | undefined;\n if (\n Array.isArray(arg) &&\n arg.length === 2 &&\n typeof arg[0] === 'string' &&\n typeof arg[1] === 'string'\n ) {\n logicalId = arg[0];\n attr = arg[1];\n } else if (typeof arg === 'string') {\n const dot = arg.indexOf('.');\n if (dot > 0 && dot < arg.length - 1) {\n logicalId = arg.slice(0, dot);\n attr = arg.slice(dot + 1);\n }\n }\n if (!logicalId || !attr) return undefined;\n if (resources[logicalId]?.Type !== 'AWS::ECR::Repository') return undefined;\n const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];\n if (typeof cached === 'string' && cached.length > 0) return cached;\n // No recorded attribute. `--from-cfn-stack` resolves state from\n // `ListStackResources`, which returns physical IDs only (never the\n // `Arn` / `RepositoryUri` attributes), so the canonical\n // `ContainerImage.fromEcrRepository` join — which extracts the\n // account + region by `Fn::Select`-ing over `Fn::Split(\":\", <Arn>)` —\n // would otherwise fail here. A same-stack ECR repository's Arn /\n // RepositoryUri are deterministic, so synthesize them from the repo's\n // physical name (the `ListStackResources` physical ID) plus the\n // already-resolved account / region / partition / URL suffix (the\n // stack's own, which is where a same-stack repo lives).\n const physicalId = context?.stateResources?.[logicalId]?.physicalId;\n const p = context?.pseudoParameters;\n if (physicalId && p?.region && p.accountId) {\n if (attr === 'Arn' && p.partition) {\n return `arn:${p.partition}:ecr:${p.region}:${p.accountId}:repository/${physicalId}`;\n }\n if (attr === 'RepositoryUri' && p.urlSuffix) {\n return `${p.accountId}.dkr.ecr.${p.region}.${p.urlSuffix}/${physicalId}`;\n }\n }\n return undefined;\n }\n\n if (intrinsic === 'Fn::Split') {\n if (!Array.isArray(arg) || arg.length !== 2) return undefined;\n const argArr = arg as unknown[];\n const delim = argArr[0];\n if (typeof delim !== 'string') return undefined;\n const src = resolveImageIntrinsicAny(argArr[1], resources, context);\n if (typeof src !== 'string') return undefined;\n return src.split(delim);\n }\n\n if (intrinsic === 'Fn::Select') {\n if (!Array.isArray(arg) || arg.length !== 2) return undefined;\n const argArr = arg as unknown[];\n const rawIndex = argArr[0];\n let index: number | undefined;\n if (typeof rawIndex === 'number') {\n index = rawIndex;\n } else if (typeof rawIndex === 'string' && /^-?\\d+$/.test(rawIndex)) {\n index = Number.parseInt(rawIndex, 10);\n }\n if (index === undefined || !Number.isFinite(index)) return undefined;\n const list = resolveImageIntrinsicAny(argArr[1], resources, context);\n if (Array.isArray(list)) {\n if (index < 0 || index >= list.length) return undefined;\n const picked = list[index];\n if (typeof picked === 'string') return picked;\n return undefined;\n }\n // Some templates pass a literal array of intrinsics directly under\n // Fn::Select. Resolve each element on the fly.\n if (Array.isArray(argArr[1])) {\n const listLiteral = argArr[1] as unknown[];\n if (index < 0 || index >= listLiteral.length) return undefined;\n return resolveImageIntrinsic(listLiteral[index], resources, context);\n }\n return undefined;\n }\n\n if (intrinsic === 'Fn::Join') {\n if (!Array.isArray(arg) || arg.length !== 2) return undefined;\n const [delim, parts] = arg as [unknown, unknown];\n if (typeof delim !== 'string' || !Array.isArray(parts)) return undefined;\n const resolved: string[] = [];\n for (const part of parts) {\n const r = resolveImageIntrinsic(part, resources, context);\n if (r === undefined) return undefined;\n resolved.push(r);\n }\n return resolved.join(delim);\n }\n\n if (intrinsic === 'Fn::Sub') {\n // Reuse the single-string Fn::Sub substituter, which handles Tier 1\n // (pseudo parameters) + Tier 2 (state-recorded ECR Repository refs).\n let template: string | undefined;\n if (typeof arg === 'string') template = arg;\n else if (Array.isArray(arg) && typeof arg[0] === 'string') template = arg[0];\n if (template === undefined) return undefined;\n const out = substituteImagePlaceholders(template, resources, context);\n if (out.includes('${')) return undefined;\n return out;\n }\n\n return undefined;\n}\n\n/**\n * Replace `${AWS::AccountId}` / `${AWS::Region}` / `${AWS::Partition}` /\n * `${AWS::URLSuffix}` against `context.pseudoParameters` and same-stack\n * `${<EcrRepoLogicalId>}` / `${<EcrRepoLogicalId>.<attr>}` placeholders\n * against `context.stateResources` in a flat string. Unresolvable\n * placeholders pass through verbatim — callers detect that with a\n * post-substitution `.includes('${')` check and surface a precise error.\n *\n * Pure string-rewrite; no AWS calls. Used by both the flat-`Fn::Sub`\n * Image path (ECS `cdkl run-task`) and the `Fn::Sub` branch of\n * `resolveImageIntrinsicAny` (the nested-intrinsic resolver in this\n * file).\n */\nexport function substituteImagePlaceholders(\n flat: string,\n resources: Record<string, TemplateResource>,\n context: ImageResolutionContext | undefined\n): string {\n if (!flat.includes('${')) return flat;\n return flat.replace(/\\$\\{([^}]+)\\}/g, (full, key: string) => {\n if (context?.pseudoParameters) {\n if (key === 'AWS::AccountId' && context.pseudoParameters.accountId) {\n return context.pseudoParameters.accountId;\n }\n if (key === 'AWS::Region' && context.pseudoParameters.region) {\n return context.pseudoParameters.region;\n }\n if (key === 'AWS::Partition' && context.pseudoParameters.partition) {\n return context.pseudoParameters.partition;\n }\n if (key === 'AWS::URLSuffix' && context.pseudoParameters.urlSuffix) {\n return context.pseudoParameters.urlSuffix;\n }\n }\n if (context?.stateResources) {\n const dot = key.indexOf('.');\n const logicalId = dot === -1 ? key : key.slice(0, dot);\n const refResource = resources[logicalId];\n const stateEntry = context.stateResources[logicalId];\n if (refResource?.Type === 'AWS::ECR::Repository' && stateEntry) {\n if (dot === -1) {\n // `${<Repo>}` → the repository's physical id (its Name).\n return stateEntry.physicalId;\n }\n const attr = key.slice(dot + 1);\n const cached = stateEntry.attributes?.[attr];\n if (typeof cached === 'string') return cached;\n }\n }\n return full;\n });\n}\n","export function stringifyValue(value: unknown): string {\n switch (typeof value) {\n case 'string':\n return value;\n case 'number':\n case 'boolean':\n case 'bigint':\n return String(value);\n case 'symbol':\n return value.toString();\n case 'undefined':\n return 'undefined';\n case 'function':\n return value.name ? `[Function: ${value.name}]` : '[Function]';\n case 'object':\n if (value === null) return 'null';\n try {\n const json = JSON.stringify(value);\n if (json !== undefined) return json;\n } catch {\n // Fall through to a stable object tag when JSON serialization fails.\n }\n return Object.prototype.toString.call(value);\n }\n}\n","import { existsSync, statSync } from 'node:fs';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport type { StackInfo } from '../synthesis/assembly-reader.js';\nimport type { TemplateResource } from '../types/resource.js';\nimport { buildCdkPathIndex, resolveCdkPathToLogicalIds } from '../cli/cdk-path.js';\nimport { matchStacks } from '../cli/stack-matcher.js';\nimport { derivePseudoParametersFromRegion, tryResolveImageFnJoin } from './intrinsic-image.js';\nimport { stringifyValue } from '../utils/stringify.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Result of resolving a `cdkl invoke <target>` argument back to a\n * concrete Lambda function in the synthesized assembly.\n *\n * Discriminated union (PR 5, D5.3): `kind === 'zip'` for traditional\n * Node.js / Python ZIP-packaged Lambdas; `kind === 'image'` for container\n * Lambdas (`Code.ImageUri`). The two variants have meaningfully different\n * fields — `runtime` / `handler` / `codePath` are zip-only, while\n * `dockerSource` / `imageConfig` / `architecture` are image-only — so the\n * compiler can enforce exhaustive handling at the consumer (the\n * `local-invoke.ts` CLI command branch).\n *\n * Orthogonal future fields (e.g. PR 6 layers) live on the base interface\n * so they apply to both variants without each adding a copy.\n */\nexport type ResolvedLambda = ResolvedZipLambda | ResolvedImageLambda;\n\ninterface ResolvedLambdaBase {\n /** Stack the function belongs to. */\n stack: StackInfo;\n /** CloudFormation logical ID of the function. */\n logicalId: string;\n /** Raw template entry (for property reads beyond what's surfaced here). */\n resource: TemplateResource;\n /** `MemorySize` from the template, or 128 when omitted (Lambda default). */\n memoryMb: number;\n /** `Timeout` (seconds) from the template, or 3 when omitted (Lambda default). */\n timeoutSec: number;\n /**\n * Resolved Lambda layers (PR 6 of #224, issue #232). Each entry points\n * at an `AWS::Lambda::LayerVersion` resource in the same stack — the\n * `logicalId` lets the caller emit clearer error messages, `assetPath`\n * is the absolute directory under `cdk.out` (resolved via the same\n * `Metadata['aws:asset:path']` hint Lambda code uses) that bind-mounts\n * at `/opt`. `[]` when the function declares no Layers.\n *\n * **Order is load-bearing**: AWS layer semantics are \"last layer wins\n * on file collision\", so this array preserves the template's input\n * order. cdk-local implements the last-wins rule by `cpSync`-merging every\n * layer's asset directory into a single host tmpdir IN TEMPLATE ORDER\n * (later layers overwrite earlier files via `recursive: true, force:\n * true`), then bind-mounting the merged tmpdir at `/opt:ro`. Docker\n * rejects multiple `-v ...:/opt:ro` entries at the same target path\n * (`Error response from daemon: Duplicate mount point: /opt`) — bind\n * mounts are NOT layered the way the OCI image stack is — so the\n * merge happens on the host, not via overlay layering. The single-\n * layer case skips the copy and bind-mounts the asset dir directly.\n *\n * Out of scope for v1 (any of these hard-error at resolution time):\n * - Cross-stack / cross-account / cross-region layer ARNs (anything\n * that isn't a same-stack `Ref` / `Fn::GetAtt[..., Ref]` pointing\n * at an `AWS::Lambda::LayerVersion`).\n * - Layers without `Metadata['aws:asset:path']` (i.e. layers whose\n * content is `S3Bucket`/`S3Key` from outside cdk.out — there's no\n * local directory to bind-mount).\n */\n layers: ResolvedLambdaLayer[];\n /**\n * `Properties.EphemeralStorage.Size` (issue #440). CDK 2.x's\n * `lambda.Function({ ephemeralStorageSize: cdk.Size.gibibytes(N) })`\n * synthesizes `Properties.EphemeralStorage: { Size: <N * 1024> }`\n * — the value is the templated `/tmp` cap in **MiB** (CFn property\n * range 512..10240). Threaded through to docker's `--tmpfs\n * /tmp:rw,size=<N>m` so handlers that exceed the deployed cap fail\n * locally with `ENOSPC` the way they would on AWS, and handlers\n * that detect free space via `statvfs` / `df` see the templated\n * size rather than the host's overlay-fs.\n *\n * Undefined when `Properties.EphemeralStorage` is absent — the\n * container's `/tmp` is then whatever the base image provides (AWS\n * Lambda base images don't mount a sized tmpfs themselves, so this\n * preserves the pre-#440 behavior). Applies to both ZIP and IMAGE\n * Lambdas — `--tmpfs` overlays inside container Lambdas just like\n * it does on the public base images.\n */\n ephemeralStorageMb?: number;\n}\n\n/**\n * One entry of a Lambda's resolved `Properties.Layers`. Two shapes:\n *\n * - `kind: 'asset'` — same-stack `AWS::Lambda::LayerVersion`\n * reference (the original PR 6 path). `assetPath` is the absolute\n * directory under `cdk.out` ready to bind-mount at `/opt`.\n * - `kind: 'arn'` — pre-existing literal-ARN entry the CDK template\n * points at directly (AWS Lambda Powertools, Datadog Extension,\n * shared internal layers, cross-account / cross-region references).\n * The layer ZIP is NOT yet on disk; the CLI materializes it via\n * `materializeLayerFromArn(...)` (issue #448) right before the\n * docker container starts. Carrying the parsed ARN fields here\n * keeps the resolver pure-functional (no AWS SDK calls) and lets\n * the materializer be tested independently.\n */\nexport type ResolvedLambdaLayer = ResolvedAssetLambdaLayer | ResolvedArnLambdaLayer;\n\nexport interface ResolvedAssetLambdaLayer {\n kind: 'asset';\n /**\n * CFn logical ID of the `AWS::Lambda::LayerVersion` resource.\n * Shared field name with the `kind: 'arn'` variant so callers can\n * read a uniform identifier without first narrowing the union.\n */\n logicalId: string;\n /**\n * Absolute path on disk to the layer's unzipped asset directory. Will\n * be bind-mounted at `/opt` inside the container (read-only). The\n * directory is laid out per AWS's runtime-specific load-path\n * conventions (`opt/python/...`, `opt/nodejs/...`, etc.) — cdk-local does\n * NOT inspect the contents, just hands the directory to docker.\n */\n assetPath: string;\n}\n\nexport interface ResolvedArnLambdaLayer {\n kind: 'arn';\n /**\n * Pseudo-logical-id for log lines — set to the literal ARN so\n * iteration code like `layers.map((l) => l.logicalId)` works\n * uniformly across both variants without per-kind narrowing.\n */\n logicalId: string;\n /**\n * Full literal ARN as it appeared in the template\n * (`arn:aws:lambda:<region>:<account>:layer:<name>:<version>`). Kept\n * verbatim alongside `logicalId` because callers (the materializer)\n * need the canonical ARN string for SDK calls and the per-kind\n * branch is the only place where the difference matters.\n */\n arn: string;\n /** Region segment extracted from the ARN (e.g. `us-east-1`). */\n region: string;\n /** Account ID segment extracted from the ARN (12 digits). */\n accountId: string;\n /** Layer name segment (the `:layer:<name>:` middle). */\n name: string;\n /** Numeric version segment, as a string for `LayerName:Version` joins. */\n version: string;\n}\n\nexport interface ResolvedZipLambda extends ResolvedLambdaBase {\n kind: 'zip';\n /** Lambda runtime string (e.g. `nodejs20.x`). */\n runtime: string;\n /** Lambda handler string (e.g. `index.handler`). */\n handler: string;\n /**\n * Resolved local code path. For asset-backed functions, this is the\n * absolute directory under `cdk.out` named by the resource's\n * `Metadata['aws:asset:path']`. For inline `Code.ZipFile` functions,\n * this is `null` and the caller is expected to materialize a temp dir\n * before bind-mounting (handled in the command layer to keep this\n * module side-effect-free).\n */\n codePath: string | null;\n /**\n * For inline Lambdas only: the inline source body. The command layer\n * writes this into a temp dir at the path implied by `handler`.\n */\n inlineCode?: string;\n}\n\nexport interface ResolvedImageLambda extends ResolvedLambdaBase {\n kind: 'image';\n /**\n * Raw `Code.ImageUri` from the template. Used to extract the asset hash\n * for the local-build path AND for the ECR-pull fallback path (when the\n * URI doesn't match any cdk.out asset). Already resolved through\n * cdk-assets bootstrap-placeholder substitution upstream — `${AWS::*}`\n * pseudo-parameters are still present (cdk-local substitutes them at the\n * lookup site since it knows the calling account/region).\n */\n imageUri: string;\n /**\n * `ImageConfig` from the template. All fields are optional — the\n * common case is just `Command: [<handler>]`. Empty `[]` for\n * `entryPoint` means \"use the image's default entrypoint\" (typically\n * `/lambda-entrypoint.sh` on AWS base images, which routes to RIE).\n */\n imageConfig: {\n command?: string[];\n entryPoint?: string[];\n workingDirectory?: string;\n };\n /**\n * `Architectures: [x86_64]` (default) or `[arm64]`. Threaded through to\n * `--platform linux/amd64` / `linux/arm64` on BOTH `docker build` AND\n * `docker run`. Without this, an arm64 host running an x86_64 Lambda\n * hits emulation; an x86_64 host running arm64 fails with\n * `exec format error`.\n */\n architecture: 'x86_64' | 'arm64';\n}\n\nexport class LocalInvokeResolutionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LocalInvokeResolutionError';\n Object.setPrototypeOf(this, LocalInvokeResolutionError.prototype);\n }\n}\n\n/**\n * Parse a `target` argument into (optional stack pattern, path-or-id).\n *\n * Two accepted forms:\n * - `Stack:LogicalId` — colon delimits stack from logical ID. Logical\n * IDs cannot contain `/` or `:`, so the parse is unambiguous.\n * - `Stack/Path/...` — display-path form. The stack prefix is the first\n * `/`-delimited segment; everything after is the construct path\n * (which itself starts with the same stack name in CDK output, e.g.\n * `MyStack/MyApi/Handler`).\n *\n * For single-stack apps the stack prefix may be omitted entirely:\n * - Bare `Handler` is treated as a logical ID in the only stack.\n * - Bare `MyApi/Handler` is treated as a construct path; the only\n * stack's name is prepended at lookup time.\n *\n * Returns the raw split. The actual stack-resolution + auto-detect logic\n * lives in `resolveLambdaTarget` so `parseTarget` stays a pure string\n * splitter.\n */\nexport interface ParsedTarget {\n /**\n * Stack pattern if explicit, else `null`. When `null` the resolver\n * auto-detects the single stack in the app.\n */\n stackPattern: string | null;\n /** Path-or-id portion of the target. */\n pathOrId: string;\n /** `true` iff `pathOrId` looks like a construct path (contains `/`). */\n isPath: boolean;\n}\n\nexport function parseTarget(target: string): ParsedTarget {\n if (typeof target !== 'string' || target.length === 0) {\n throw new LocalInvokeResolutionError(\n \"Empty target. Pass a CDK display path (e.g. 'MyStack/MyApi/Handler') or stack-qualified logical ID (e.g. 'MyStack:MyApiHandler1234ABCD').\"\n );\n }\n\n // Stack:LogicalId form. The colon must precede every slash for this to\n // be the colon form (otherwise `Stack:Foo/bar` is ambiguous and we\n // prefer the path form).\n const colonIdx = target.indexOf(':');\n const slashIdx = target.indexOf('/');\n if (colonIdx > 0 && (slashIdx === -1 || colonIdx < slashIdx)) {\n const stackPattern = target.substring(0, colonIdx);\n const pathOrId = target.substring(colonIdx + 1);\n if (pathOrId.length === 0) {\n throw new LocalInvokeResolutionError(`Target '${target}' has no logical ID after ':'.`);\n }\n return { stackPattern, pathOrId, isPath: pathOrId.includes('/') };\n }\n\n // Path form with explicit stack: stack is the first segment.\n if (slashIdx > 0) {\n return { stackPattern: target.substring(0, slashIdx), pathOrId: target, isPath: true };\n }\n\n // Bare logical ID — single-stack auto-detect path.\n return { stackPattern: null, pathOrId: target, isPath: false };\n}\n\n/**\n * Resolve a parsed target against the synthesized stacks. Throws\n * {@link LocalInvokeResolutionError} with an actionable message (listing\n * available Lambdas) on any miss.\n */\nexport function resolveLambdaTarget(target: string, stacks: StackInfo[]): ResolvedLambda {\n if (stacks.length === 0) {\n throw new LocalInvokeResolutionError('No stacks found in the synthesized assembly.');\n }\n\n const parsed = parseTarget(target);\n const stack = pickStack(parsed, stacks);\n\n const template = stack.template;\n const resources = template.Resources ?? {};\n\n let match: { logicalId: string; resource: TemplateResource } | undefined;\n\n if (parsed.isPath) {\n // Build the path index once so we can list every available Lambda\n // when the lookup misses.\n const index = buildCdkPathIndex(template);\n const resolvedPaths = resolveCdkPathToLogicalIds(parsed.pathOrId, index);\n\n // Filter to Lambda functions; keep the rest for an error path.\n const lambdaMatches = resolvedPaths.filter(\n ({ logicalId }) => resources[logicalId]?.Type === 'AWS::Lambda::Function'\n );\n\n if (lambdaMatches.length === 0) {\n throw notFoundError(target, stack, resources);\n }\n if (lambdaMatches.length > 1) {\n throw new LocalInvokeResolutionError(\n `Target '${target}' matches ${lambdaMatches.length} Lambda functions in ${stack.stackName}: ` +\n lambdaMatches.map((m) => m.logicalId).join(', ') +\n '. Refine the path or use the stack:LogicalId form.'\n );\n }\n const m = lambdaMatches[0]!;\n match = { logicalId: m.logicalId, resource: resources[m.logicalId]! };\n } else {\n const resource = resources[parsed.pathOrId];\n if (!resource) {\n throw notFoundError(target, stack, resources);\n }\n match = { logicalId: parsed.pathOrId, resource };\n }\n\n const { logicalId, resource } = match;\n\n if (resource.Type !== 'AWS::Lambda::Function') {\n if (resource.Type.startsWith('Custom::')) {\n throw new LocalInvokeResolutionError(\n `Resource '${logicalId}' in ${stack.stackName} is a Custom Resource (${resource.Type}), not a Lambda function. ` +\n `Custom Resources are invoked by the deploy framework, not by users. ` +\n `If you want to test the underlying handler, target the ServiceToken Lambda directly.`\n );\n }\n throw new LocalInvokeResolutionError(\n `Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not a Lambda function. ` +\n `${getEmbedConfig().cliName} invoke only works on AWS::Lambda::Function resources in v1.`\n );\n }\n\n return extractLambdaProperties(stack, logicalId, resource, resources);\n}\n\n/**\n * Single-stack auto-detect (D4): if the app has exactly one stack, the\n * user may omit the stack prefix. Otherwise an explicit stack pattern is\n * required.\n */\nfunction pickStack(parsed: ParsedTarget, stacks: StackInfo[]): StackInfo {\n if (parsed.stackPattern === null) {\n if (stacks.length === 1) return stacks[0]!;\n throw new LocalInvokeResolutionError(\n `Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. ` +\n `Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). ` +\n `Available stacks: ${stacks.map((s) => s.stackName).join(', ')}.`\n );\n }\n\n // Reuse the shared stack-matcher so display-path / wildcard semantics\n // line up with deploy / diff / destroy.\n const matched = matchStacks(stacks, [parsed.stackPattern]);\n if (matched.length === 0) {\n throw new LocalInvokeResolutionError(\n `Stack '${parsed.stackPattern}' not found. ` +\n `Available stacks: ${stacks.map((s) => s.stackName).join(', ')}.`\n );\n }\n if (matched.length > 1) {\n throw new LocalInvokeResolutionError(\n `Stack pattern '${parsed.stackPattern}' matched ${matched.length} stacks: ` +\n matched.map((s) => s.stackName).join(', ') +\n '. Use a more specific pattern.'\n );\n }\n return matched[0]!;\n}\n\n/**\n * Pull the Lambda properties this command cares about out of the\n * template. Validates required fields up front so the docker-runner can\n * assume a fully-typed `ResolvedLambda`.\n *\n * Branches on `Code.ImageUri`: when set the function is a container\n * Lambda (PR 5, D5.3) and the discriminator flips to `kind: 'image'`;\n * `Runtime` / `Handler` are NOT required on this path (D5.5 — AWS\n * contract: container Lambdas don't have `Handler`; invocation is\n * driven by `ImageConfig.Command` or the image's own CMD).\n */\nfunction extractLambdaProperties(\n stack: StackInfo,\n logicalId: string,\n resource: TemplateResource,\n resources: Record<string, TemplateResource>\n): ResolvedLambda {\n const props = resource.Properties ?? {};\n const memoryMb = typeof props['MemorySize'] === 'number' ? props['MemorySize'] : 128;\n const timeoutSec = typeof props['Timeout'] === 'number' ? props['Timeout'] : 3;\n const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);\n\n const code = (props['Code'] ?? {}) as Record<string, unknown>;\n const imageUri = extractImageUri(\n code['ImageUri'],\n logicalId,\n stack.stackName,\n resources,\n stack.region\n );\n\n if (imageUri !== undefined) {\n return extractImageLambdaProperties({\n stack,\n logicalId,\n resource,\n memoryMb,\n timeoutSec,\n props,\n imageUri,\n // Spread-and-omit so the optional field stays optional at the\n // callee under `exactOptionalPropertyTypes` — passing `undefined`\n // for `ephemeralStorageMb?: number` would be a type error.\n ...(ephemeralStorageMb !== undefined && { ephemeralStorageMb }),\n });\n }\n\n // ZIP path (D5.5): Runtime + Handler are mandatory.\n const runtime = typeof props['Runtime'] === 'string' ? props['Runtime'] : '';\n const handler = typeof props['Handler'] === 'string' ? props['Handler'] : '';\n\n if (!runtime) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. ` +\n `${getEmbedConfig().productName} cannot tell if this is a ZIP or a container Lambda.`\n );\n }\n if (!handler) {\n throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Handler property.`);\n }\n\n const inlineCode = typeof code['ZipFile'] === 'string' ? code['ZipFile'] : undefined;\n\n let codePath: string | null = null;\n if (!inlineCode) {\n codePath = resolveAssetCodePath(stack, logicalId, resource);\n }\n\n // PR 6 (#232): resolve same-stack `Layers` references. Out-of-scope\n // shapes (literal ARNs, cross-stack refs, layers without an asset\n // path) hard-error here so the user sees a clear pointer at the\n // offending entry instead of a silently-missing `/opt/<lib>` at\n // invoke time.\n const layers = resolveLambdaLayers(stack, logicalId, props);\n\n return {\n kind: 'zip',\n stack,\n logicalId,\n resource,\n runtime,\n handler,\n memoryMb,\n timeoutSec,\n codePath,\n layers,\n ...(ephemeralStorageMb !== undefined && { ephemeralStorageMb }),\n ...(inlineCode !== undefined && { inlineCode }),\n };\n}\n\n/**\n * Parse `Properties.EphemeralStorage.Size` (issue #440). CFn shape:\n * `{ EphemeralStorage: { Size: <MiB> } }`. CDK's\n * `cdk.Size.gibibytes(N)` serializes to `N * 1024`. AWS-side range is\n * 512..10240 MiB (the deployed function rejects anything outside that\n * range at create time); cdk-local rejects > 10240 here so a misconfigured\n * template fails fast at `cdkl invoke` boot rather than hanging\n * on a `docker run` that AWS would have refused anyway. The 512 floor\n * is AWS's minimum (the default when `EphemeralStorage` is omitted is\n * also 512), but we deliberately accept values DOWN to 1 so users can\n * exercise the cap with a deliberately-small `/tmp` in local tests —\n * `--tmpfs /tmp:size=Nm` itself enforces no lower bound; the only\n * cross-check is \"would AWS accept this?\", which the deploy side\n * already gates upstream.\n *\n * Returns `undefined` when the property is absent, NaN, < 1, or\n * non-numeric. Hard-rejects > 10240. Intrinsic-valued sizes (the\n * `{ Ref: 'SomeParam' }` shape that's uncommon for EphemeralStorage\n * but theoretically valid) drop to `undefined` with a one-line warn\n * via the calling logger — local invoke can't resolve those without\n * the template's Parameters context the deploy engine has, and the\n * fallback (no `--tmpfs`) is safer than guessing.\n */\nexport function extractEphemeralStorageMb(\n props: Record<string, unknown>,\n logicalId: string\n): number | undefined {\n const raw = props['EphemeralStorage'];\n if (raw === undefined || raw === null) return undefined;\n if (typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const size = (raw as Record<string, unknown>)['Size'];\n if (typeof size !== 'number') {\n // Intrinsic-valued or otherwise unresolvable. Drop silently and\n // leave `--tmpfs` off — the deploy side enforces the real range\n // upstream. The `logicalId` argument is kept for parity with the\n // sibling extractors (so a future audit can grep call sites).\n void logicalId;\n return undefined;\n }\n if (!Number.isFinite(size) || size < 1) return undefined;\n if (size > 10240) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' has Properties.EphemeralStorage.Size = ${size} MiB, ` +\n 'which exceeds the AWS limit of 10240 MiB. AWS would reject the function at deploy time; ' +\n 'cap the value to <= 10240 (10 GiB) and retry.'\n );\n }\n // CFn templates may carry fractional MiB values (unusual, but the\n // type is `number`). docker's `--tmpfs size=...m` parser accepts\n // integers only — round down to the nearest MiB to be safe; the\n // worst-case effect is a slightly smaller `/tmp` than templated,\n // which still surfaces the ENOSPC the user wants to catch.\n return Math.floor(size);\n}\n\n/**\n * Extract the `Code.ImageUri` value across the shapes CDK actually synthesizes.\n *\n * Supported shapes:\n *\n * 1. Flat string — pass through.\n * 2. `Fn::Sub` (string or `[template, vars]`) — the canonical asset\n * shape for `lambda.DockerImageCode.fromImageAsset(...)`. The\n * `${AWS::*}` placeholders survive and are substituted at the\n * cdk-assets lookup site. Critical bug fix C1 from the PR 5 design\n * doc: CDK synthesizes\n * `{Fn::Sub: '${AWS::AccountId}.dkr.ecr.${AWS::Region}.${AWS::URLSuffix}/cdk-hnb659fds-container-assets-${AWS::AccountId}-${AWS::Region}:<hash>'}`,\n * NOT a flat string. The hash-extraction regex in the asset\n * manifest loader works against the substituted form.\n * 3. `Fn::Join` (canonical CDK 2.x shape for\n * `lambda.DockerImageCode.fromEcr(repo, { tagOrDigest })`) — see\n * [src/local/intrinsic-image.ts](./intrinsic-image.ts), `tryResolveImageFnJoin`.\n * For IMPORTED repositories (literal acct-id / region + `Ref:\n * AWS::URLSuffix` + literal repo path) the resolver returns a\n * complete ECR URI here without state. For SAME-STACK references\n * the resolver needs the host's state (`--from-state`) to recover the\n * repository's account-id / region; without state we surface a\n * clear error pointing the user at `cdkl invoke --from-state`\n * / `ContainerImage.fromAsset` / a public-image alternative.\n *\n * Throws `LocalInvokeResolutionError` for `Fn::Join` shapes the resolver\n * recognizes as ECR-shape-needing-state OR malformed; returns `undefined`\n * for genuinely unrecognized shapes so the caller's downstream ZIP-vs-\n * IMAGE branching can route to its existing error path.\n */\nfunction extractImageUri(\n value: unknown,\n logicalId: string,\n stackName: string,\n resources: Record<string, TemplateResource>,\n region: string | undefined\n): string | undefined {\n if (typeof value === 'string' && value.length > 0) return value;\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const obj = value as Record<string, unknown>;\n const sub = obj['Fn::Sub'];\n if (typeof sub === 'string' && sub.length > 0) return sub;\n // Fn::Sub array form: [template, vars]. The first element is the template.\n if (Array.isArray(sub) && typeof sub[0] === 'string') return sub[0];\n\n // `Fn::Join` — try the shared ECR-URI resolver. Issue #637 plumbed\n // region-derived pseudo parameters (`urlSuffix` / `partition` /\n // `region`) through here so the canonical\n // `lambda.DockerImageCode.fromImageAsset` shape (only intrinsic in\n // the URI is `${AWS::URLSuffix}`) resolves without `--from-state`.\n // Same-stack ECR refs still return `needs-state`; a Join that\n // genuinely references `${AWS::AccountId}` without state returns\n // `not-applicable` with a more specific error.\n if ('Fn::Join' in obj) {\n const pseudoParameters = derivePseudoParametersFromRegion(region);\n const joinResolved = tryResolveImageFnJoin(\n value,\n resources,\n pseudoParameters ? { pseudoParameters } : undefined\n );\n if (joinResolved.kind === 'resolved') return joinResolved.uri;\n if (joinResolved.kind === 'needs-state') {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. ` +\n `${getEmbedConfig().cliName} invoke cannot resolve the repository URI without state — ` +\n `deploy the stack first (so ${getEmbedConfig().productName} records the repository physical id), ` +\n 'rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.'\n );\n }\n if (joinResolved.kind === 'unsupported-join') {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. ` +\n `${getEmbedConfig().cliName} invoke recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape ` +\n '(delimiter \"\" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).'\n );\n }\n // `not-applicable` — Join couldn't reduce every element AND no\n // same-stack ECR Repository ref. With #637's pseudo-parameter\n // plumbing the typical remaining cause is `${AWS::AccountId}`\n // (needs an STS call or `--from-state`) or an unknown region.\n const accountIdHint = pseudoParameters\n ? ` (likely \\${AWS::AccountId}, which ${getEmbedConfig().productName} cannot derive without --from-state or STS)`\n : ` (${getEmbedConfig().productName} could not derive AWS pseudo parameters because stack.region was undefined)`;\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that ${getEmbedConfig().cliName} invoke cannot resolve${accountIdHint}. ` +\n 'Workarounds: deploy first and run with --from-state, or pin a fully-literal public image URI.'\n );\n }\n }\n return undefined;\n}\n\n/**\n * Build the IMAGE-variant `ResolvedLambda` from a Lambda template entry\n * with `Code.ImageUri`. `ImageConfig` and `Architectures` are both\n * optional in CFn — the defaults match the AWS-side defaults.\n */\nfunction extractImageLambdaProperties(args: {\n stack: StackInfo;\n logicalId: string;\n resource: TemplateResource;\n memoryMb: number;\n timeoutSec: number;\n ephemeralStorageMb?: number;\n props: Record<string, unknown>;\n imageUri: string;\n}): ResolvedImageLambda {\n const { stack, logicalId, resource, memoryMb, timeoutSec, ephemeralStorageMb, props, imageUri } =\n args;\n\n const rawImageConfig = (props['ImageConfig'] ?? {}) as Record<string, unknown>;\n const imageConfig: ResolvedImageLambda['imageConfig'] = {};\n if (Array.isArray(rawImageConfig['Command'])) {\n imageConfig.command = rawImageConfig['Command'].filter(\n (s): s is string => typeof s === 'string'\n );\n }\n if (Array.isArray(rawImageConfig['EntryPoint'])) {\n imageConfig.entryPoint = rawImageConfig['EntryPoint'].filter(\n (s): s is string => typeof s === 'string'\n );\n }\n if (typeof rawImageConfig['WorkingDirectory'] === 'string') {\n imageConfig.workingDirectory = rawImageConfig['WorkingDirectory'];\n }\n\n // Architectures is an array (CFn). CDK never sets more than one entry.\n // Default x86_64 matches AWS.\n const arches = props['Architectures'];\n let architecture: 'x86_64' | 'arm64' = 'x86_64';\n if (Array.isArray(arches) && arches.length > 0) {\n const first: unknown = arches[0];\n if (first === 'arm64') architecture = 'arm64';\n else if (first === 'x86_64') architecture = 'x86_64';\n else {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' has unsupported Architectures value '${String(first)}'. ` +\n `${getEmbedConfig().cliName} invoke supports x86_64 and arm64.`\n );\n }\n }\n\n // PR 6 (#232): container Lambdas reject `Layers` at deploy time on\n // the AWS side — layers are baked into the image at build time, not\n // overlaid at runtime. We silently ignore any `Layers` property here\n // (matches AWS behavior at invoke time) by passing an empty list.\n return {\n kind: 'image',\n stack,\n logicalId,\n resource,\n memoryMb,\n timeoutSec,\n imageUri,\n imageConfig,\n architecture,\n layers: [],\n ...(ephemeralStorageMb !== undefined && { ephemeralStorageMb }),\n };\n}\n\n/**\n * Resolve the local directory that corresponds to a function's deployed\n * asset, using the CDK-blessed `Metadata['aws:asset:path']` hint (D2). The\n * value is a directory path relative to `cdk.out` (e.g. `asset.abc123def`)\n * and CDK has already unzipped it for us — we bind-mount the directory\n * directly, no re-zipping.\n *\n * Falls back to a clear error when the metadata is missing OR the resolved\n * directory does not exist (CDK should always emit it for asset-backed\n * Lambdas; absence usually means the user pre-synthesized with a different\n * cdk.out and pointed `--output` at a stale one).\n */\nfunction resolveAssetCodePath(\n stack: StackInfo,\n logicalId: string,\n resource: TemplateResource\n): string {\n const meta = resource.Metadata;\n const assetPath = meta?.['aws:asset:path'];\n if (typeof assetPath !== 'string' || assetPath.length === 0) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' has no Metadata['aws:asset:path']. ` +\n `${getEmbedConfig().cliName} invoke needs this hint to find the local asset directory. ` +\n 'Re-synthesize the app (without `--output <stale-dir>`) and retry.'\n );\n }\n\n // Asset paths are typically relative to cdk.out. The stack's\n // `assetManifestPath` is `<cdk.out>/<stack>.assets.json`; we strip the\n // filename to get the assembly directory. As a fallback (e.g. for\n // stacks with no asset manifest), use the dirname of the template\n // path implicit in the stack info — but in v1 every Lambda-bearing\n // stack has an asset manifest, so the fallback is mostly defensive.\n const cdkOutDir = stack.assetManifestPath ? dirname(stack.assetManifestPath) : process.cwd();\n\n const abs = isAbsolute(assetPath) ? assetPath : resolve(cdkOutDir, assetPath);\n if (!existsSync(abs) || !statSync(abs).isDirectory()) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' asset directory '${abs}' does not exist or is not a directory. ` +\n 'Re-synthesize the app and retry.'\n );\n }\n return abs;\n}\n\n/**\n * Resolve a Lambda's `Properties.Layers` references to local asset\n * directories (PR 6 of #224, issue #232).\n *\n * Each entry in the synthesized template is an intrinsic pointing at an\n * `AWS::Lambda::LayerVersion` resource in the same stack — most commonly\n * `{Ref: '<LayerLogicalId>'}` (which CDK uses for `LayerVersion.layerArn`)\n * or `{Fn::GetAtt: ['<LayerLogicalId>', 'Ref']}`. Once we have the\n * layer's logical ID we look up its `aws:asset:path` Metadata the same\n * way function code is located (the layer asset is unzipped under\n * `cdk.out/asset.<hash>/` ready to bind-mount).\n *\n * **Order is preserved**: `Properties.Layers` is iterated left-to-right\n * and the resulting `ResolvedLambdaLayer[]` carries the same order. The\n * caller (`local-invoke.ts`'s `materializeLambdaLayers` and\n * `local-start-api.ts`'s server-boot pre-merge) `cpSync`-merges every\n * entry into one host tmpdir in template order to honor AWS's\n * \"last-layer-wins\" file-collision semantics — Docker rejects multiple\n * bind mounts at the same target so cdk-local cannot rely on overlay\n * layering.\n *\n * **Same-stack handling** (`{Ref: <Id>}` / `{Fn::GetAtt: [<Id>, 'Ref']}`):\n *\n * - Refs that don't point at an `AWS::Lambda::LayerVersion` resource\n * hard-error — almost always a typo'd logical ID.\n * - Refs to a `LayerVersion` whose `Metadata['aws:asset:path']` is\n * missing hard-error — the layer's content is `S3Bucket` / `S3Key`\n * from outside cdk.out and there's no local directory to bind-mount.\n *\n * **Literal-ARN handling** (issue #448): entries shaped like the string\n * `arn:aws:lambda:<region>:<account>:layer:<name>:<version>` are parsed\n * into a `{kind: 'arn', ...}` resolved layer. The actual\n * `lambda:GetLayerVersion` + presigned-URL download + unzip happens\n * later in the CLI (`materializeLayerFromArn(...)`), which can optionally\n * `sts:AssumeRole` into the layer's account when the dev's default\n * credentials cannot read it. Covers AWS-published public layers (Lambda\n * Powertools, Datadog Extension, etc.) and cross-account / cross-region\n * shared layers.\n */\nexport function resolveLambdaLayers(\n stack: StackInfo,\n logicalId: string,\n props: Record<string, unknown>\n): ResolvedLambdaLayer[] {\n const layers = props['Layers'];\n if (layers === undefined) return [];\n if (!Array.isArray(layers)) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' has a non-array Layers property. Expected an array of LayerVersion references.`\n );\n }\n if (layers.length === 0) return [];\n\n const resources = stack.template.Resources ?? {};\n const out: ResolvedLambdaLayer[] = [];\n for (let i = 0; i < layers.length; i++) {\n const entry: unknown = layers[i];\n\n // Literal-ARN entry (issue #448) — recognized before the\n // logical-ID lookup so users who reference AWS-published layers\n // (Lambda Powertools etc.) or cross-account / cross-region shared\n // layers bypass the same-stack resource scan.\n if (typeof entry === 'string') {\n const parsed = parseLayerVersionArn(entry);\n if (!parsed) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' has a Layers entry [${i}] ${getEmbedConfig().productName} cannot resolve locally: literal string '${entry}'. ` +\n 'Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion ' +\n 'OR a literal layer-version ARN of the form ' +\n 'arn:aws:lambda:<region>:<account>:layer:<name>:<version>.'\n );\n }\n out.push({ kind: 'arn', logicalId: parsed.arn, ...parsed });\n continue;\n }\n\n const layerLogicalId = pickLayerLogicalId(entry);\n if (!layerLogicalId) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' has a Layers entry [${i}] ${getEmbedConfig().productName} cannot resolve locally: ${describeLayerEntry(entry)}. ` +\n 'Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion ' +\n 'OR a literal layer-version ARN of the form ' +\n 'arn:aws:lambda:<region>:<account>:layer:<name>:<version>.'\n );\n }\n\n const layerResource = resources[layerLogicalId];\n if (!layerResource) {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}', ` +\n `but no resource with that logical ID exists in stack '${stack.stackName}'.`\n );\n }\n if (layerResource.Type !== 'AWS::Lambda::LayerVersion') {\n throw new LocalInvokeResolutionError(\n `Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}' (${layerResource.Type}), ` +\n 'which is not an AWS::Lambda::LayerVersion.'\n );\n }\n\n const assetPath = resolveAssetCodePath(stack, layerLogicalId, layerResource);\n out.push({ kind: 'asset', logicalId: layerLogicalId, assetPath });\n }\n return out;\n}\n\n/**\n * Parse a Lambda layer-version ARN string into its segments.\n *\n * Returns `undefined` for anything that does not match the strict\n * `arn:aws:lambda:<region>:<account>:layer:<name>:<version>` shape so\n * the caller can produce a clearer error than a silent\n * misinterpretation of hand-edited templates. The partition segment\n * accepts `aws` / `aws-cn` / `aws-us-gov` so GovCloud / China-region\n * ARNs work without code changes.\n *\n * Exported for unit testing.\n */\nexport function parseLayerVersionArn(\n input: string\n): { arn: string; region: string; accountId: string; name: string; version: string } | undefined {\n // Region segment accepts up to two interior `<word>-` chunks before\n // the numeric suffix so GovCloud (`us-gov-west-1`) / China\n // (`cn-north-1`) / standard (`us-east-1`) regions all match.\n const m =\n /^arn:(aws|aws-cn|aws-us-gov):lambda:([a-z]{2}-(?:[a-z]+-){1,2}\\d+):(\\d{12}):layer:([A-Za-z0-9_-]+):(\\d+)$/.exec(\n input\n );\n if (!m) return undefined;\n return {\n arn: input,\n region: m[2]!,\n accountId: m[3]!,\n name: m[4]!,\n version: m[5]!,\n };\n}\n\n/**\n * Walk a single Layers-array entry and return the referenced layer's\n * logical ID — or `undefined` for shapes we don't try to resolve in v1.\n *\n * Accepted shapes (what CDK actually synthesizes — JSON-only):\n * - `{Ref: '<LayerLogicalId>'}`\n * - `{Fn::GetAtt: ['<LayerLogicalId>', 'Ref']}` (rare; LayerVersion's\n * Ref form is usually emitted as a flat `Ref`)\n *\n * Intentionally **rejected**: the YAML-only string form\n * `{Fn::GetAtt: '<LogicalId>.<attr>'}`. CloudFormation YAML accepts the\n * dot-shorthand and converts it to the array form on the wire, but\n * CloudFormation JSON (the output of `cdk synth`, which is the only\n * thing cdk-local ingests) never emits the string form. Treating it as\n * resolvable here would silently accept hand-edited / malformed templates\n * that no real CDK flow can produce; instead we fall through to the\n * standard \"cdk-local cannot resolve this Layers entry locally\" error so the\n * user sees the offending shape called out.\n */\nfunction pickLayerLogicalId(entry: unknown): string | undefined {\n if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) return undefined;\n const obj = entry as Record<string, unknown>;\n if (typeof obj['Ref'] === 'string') return obj['Ref'];\n if ('Fn::GetAtt' in obj) {\n const arg = obj['Fn::GetAtt'];\n if (Array.isArray(arg) && typeof arg[0] === 'string') return arg[0];\n // Deliberately not: `if (typeof arg === 'string') return arg.split('.')[0]`.\n // See docstring above — the string form is YAML-only and CFn JSON\n // never emits it.\n }\n return undefined;\n}\n\n/**\n * Stringify a Layers-array entry for use in error messages. Truncates\n * literal ARNs to a short form so the message stays one-line.\n */\nfunction describeLayerEntry(entry: unknown): string {\n if (typeof entry === 'string') return `literal ARN '${entry}'`;\n if (entry === null) return 'null';\n if (typeof entry !== 'object') return stringifyValue(entry);\n try {\n const json = JSON.stringify(entry);\n return json.length > 120 ? json.substring(0, 117) + '...' : json;\n } catch {\n return Object.prototype.toString.call(entry);\n }\n}\n\n/**\n * Build a \"target not found\" error that lists every Lambda function in\n * the resolved stack so the user can copy/paste a valid target. Mirrors\n * the format the issue spec calls out.\n */\nfunction notFoundError(\n target: string,\n stack: StackInfo,\n resources: Record<string, TemplateResource>\n): LocalInvokeResolutionError {\n const lambdas: { displayPath: string; logicalId: string }[] = [];\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::Lambda::Function') continue;\n const meta = resource.Metadata;\n const cdkPath = typeof meta?.['aws:cdk:path'] === 'string' ? meta['aws:cdk:path'] : '';\n lambdas.push({ displayPath: cdkPath || logicalId, logicalId });\n }\n\n let msg = `target '${target}' did not match any Lambda function in ${stack.stackName}.\\n\\n`;\n if (lambdas.length === 0) {\n msg += `Stack ${stack.stackName} has no Lambda functions.`;\n } else {\n const width = Math.max(...lambdas.map((l) => l.displayPath.length));\n msg += `Available Lambda functions in ${stack.stackName}:\\n`;\n for (const l of lambdas) {\n msg += ` ${l.displayPath.padEnd(width)} (${l.logicalId})\\n`;\n }\n }\n return new LocalInvokeResolutionError(msg.trimEnd());\n}\n","import type { StackInfo } from '../synthesis/assembly-reader.js';\nimport type { TemplateResource } from '../types/resource.js';\nimport { buildCdkPathIndex, resolveCdkPathToLogicalIds } from '../cli/cdk-path.js';\nimport { matchStacks } from '../cli/stack-matcher.js';\nimport {\n derivePseudoParametersFromRegion,\n substituteImagePlaceholders,\n tryResolveImageFnJoin,\n type ImageResolutionContext,\n} from './intrinsic-image.js';\nimport { parseTarget, type ParsedTarget } from './lambda-resolver.js';\nimport { getEmbedConfig } from './embed-config.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * CloudFormation resource type for a Bedrock AgentCore Runtime.\n * `cdkl invoke-agentcore` resolves and runs these locally.\n */\nexport const AGENTCORE_RUNTIME_TYPE = 'AWS::BedrockAgentCore::Runtime';\n\n/**\n * AgentCore Runtime protocols `cdkl invoke-agentcore` can serve.\n *\n * - `HTTP` — the agent contract (`POST /invocations` + `GET /ping` on 8080).\n * - `MCP` — Model Context Protocol over Streamable HTTP (`POST /mcp` on 8000).\n * - `A2A` — Agent2Agent JSON-RPC 2.0 over HTTP (`POST /` on 9000).\n * - `AGUI` — Agent-User Interaction event streams (SSE on `POST /invocations`,\n * WebSocket on `/ws`); reuses the HTTP path's container port (8080) and its\n * incremental SSE / WS streaming.\n */\nexport const AGENTCORE_HTTP_PROTOCOL = 'HTTP';\nexport const AGENTCORE_MCP_PROTOCOL = 'MCP';\nexport const AGENTCORE_A2A_PROTOCOL = 'A2A';\nexport const AGENTCORE_AGUI_PROTOCOL = 'AGUI';\n\n/** Protocols this CLI can run a container for. */\nconst SUPPORTED_AGENTCORE_PROTOCOLS = [\n AGENTCORE_HTTP_PROTOCOL,\n AGENTCORE_MCP_PROTOCOL,\n AGENTCORE_A2A_PROTOCOL,\n AGENTCORE_AGUI_PROTOCOL,\n] as const;\n\n/**\n * Result of resolving a `cdkl invoke-agentcore <target>` argument back to a\n * concrete `AWS::BedrockAgentCore::Runtime` in the synthesized assembly.\n *\n * Covers the CONTAINER artifact and the `CodeConfiguration` managed-runtime\n * artifact (fromCodeAsset AND fromS3) on all four protocols — HTTP / MCP /\n * A2A / AGUI. The resolver hard-errors on a non-literal `Code.S3.Prefix`\n * so the command never starts something it can't run.\n */\nexport interface ResolvedAgentCoreRuntime {\n /** Stack the runtime belongs to. */\n stack: StackInfo;\n /** CloudFormation logical ID of the runtime. */\n logicalId: string;\n /** Raw template entry (for property reads beyond what's surfaced here). */\n resource: TemplateResource;\n /**\n * Resolved container image URI from\n * `AgentRuntimeArtifact.ContainerConfiguration.ContainerUri`. Set for a\n * CONTAINER artifact; undefined for a {@link codeArtifact} one (exactly one\n * of the two is set).\n *\n * May still carry `${AWS::*}` placeholders when the source was an\n * `Fn::Sub` (the canonical `fromAsset` shape): the asset-hash match in\n * the command's image plan extracts the tag regardless, and the ECR-pull\n * path substitutes them via `--from-cfn-stack` state. A literal URI\n * passes through verbatim.\n */\n containerUri?: string;\n /**\n * Resolved `AgentRuntimeArtifact.CodeConfiguration` (managed-runtime / from\n * source) when the runtime declares one instead of a container — the command\n * builds a local image from the bundle's source. Undefined for a container\n * artifact (exactly one of {@link containerUri} / `codeArtifact` is set).\n */\n codeArtifact?: AgentCoreCodeArtifact;\n /**\n * `Properties.EnvironmentVariables` as it appears in the template\n * (a `Record<string, unknown>` — intrinsic-valued entries are left\n * unresolved here and handled by the command's shared env path). `{}`\n * when the runtime declares none.\n */\n environmentVariables: Record<string, unknown>;\n /**\n * `Properties.RoleArn` when it is a literal ARN string, else undefined\n * (an intrinsic such as `Fn::GetAtt`). Bare `--assume-role` uses this;\n * an intrinsic role falls back to an explicit `--assume-role <arn>`.\n */\n roleArn?: string;\n /** `HTTP` / `MCP` / `A2A` / `AGUI` (validated at resolution time). */\n protocol: string;\n /**\n * `Properties.AuthorizerConfiguration.CustomJWTAuthorizer` when present\n * with a literal `DiscoveryUrl` — the inbound JWT (OAuth / OIDC) authorizer\n * AgentCore uses to gate `/invocations`. Undefined when the runtime is\n * unauthenticated, or when `DiscoveryUrl` is an unresolved intrinsic.\n */\n jwtAuthorizer?: AgentCoreJwtAuthorizer;\n}\n\n/**\n * The inbound custom-JWT authorizer config a runtime declares\n * (`AuthorizerConfiguration.CustomJWTAuthorizer`). `discoveryUrl` is the\n * OIDC discovery document URL (`.well-known/openid-configuration`);\n * `allowedAudience` / `allowedClients` are the `aud` / `client_id`\n * allowlists the token must satisfy;\n * `allowedScopes` are OAuth scopes the token's `scope` claim must include;\n * `customClaims` are per-claim equality / membership rules the token must\n * satisfy in addition to the standard checks.\n */\nexport interface AgentCoreJwtAuthorizer {\n discoveryUrl: string;\n allowedAudience?: string[];\n allowedClients?: string[];\n allowedScopes?: string[];\n customClaims?: AgentCoreCustomClaim[];\n}\n\n/**\n * A single `CustomClaims` rule from\n * `AuthorizerConfiguration.CustomJWTAuthorizer.CustomClaims[]`. Drives\n * per-claim verification:\n *\n * - `valueType: STRING` + `operator: EQUALS` — the token claim must\n * string-equal `value` (a single string).\n * - `valueType: STRING_ARRAY` + `operator: CONTAINS` — the token claim must\n * be an array containing `value` (a single string).\n * - `valueType: STRING_ARRAY` + `operator: CONTAINS_ANY` — the token claim\n * must be an array sharing at least one entry with `value` (an array of\n * strings).\n */\nexport interface AgentCoreCustomClaim {\n name: string;\n valueType: 'STRING' | 'STRING_ARRAY';\n operator: 'EQUALS' | 'CONTAINS' | 'CONTAINS_ANY';\n value: string | string[];\n}\n\n/**\n * A resolved `AgentRuntimeArtifact.CodeConfiguration` (managed-runtime).\n * `codeAssetHash` is the cdk.out file-asset hash (from `Code.S3.Prefix`,\n * `<hash>.zip`) the command looks up to find the bundle's local source dir\n * (the `fromCodeAsset` shape); `entryPoint` is the `EntryPoint` argv and\n * `runtime` the `Runtime` enum.\n *\n * `s3Source` is set for the `fromS3` shape — a bundle whose `Code.S3.Bucket`\n * is either a literal string (a pre-existing S3 object — NOT the CDK staging\n * bucket of a fromCodeAsset, which renders as an `Fn::Sub` intrinsic) or an\n * unresolved intrinsic the command resolves against `--from-cfn-stack` state\n * (`Ref` / `Fn::ImportValue` / `Fn::GetStackOutput` / `Fn::Sub` — the same\n * intrinsics `--from-cfn-stack` env-var substitution already handles).\n *\n * - `bucket` is set when the template carries a literal bucket name, OR after\n * the command has resolved an intrinsic against state.\n * - `bucketIntrinsic` is set when the template's `Code.S3.Bucket` is an\n * unresolved intrinsic — the command resolves it via the same\n * state-substitution machinery env vars use, then populates `bucket`.\n *\n * Exactly one of `bucket` / `bucketIntrinsic` is set as returned by the\n * resolver (the command turns the intrinsic into a `bucket` before download).\n */\nexport interface AgentCoreCodeArtifact {\n runtime: string;\n entryPoint: string[];\n codeAssetHash: string;\n s3Source?: {\n bucket?: string;\n bucketIntrinsic?: unknown;\n key: string;\n versionId?: string;\n };\n}\n\nexport class AgentCoreResolutionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'AgentCoreResolutionError';\n Object.setPrototypeOf(this, AgentCoreResolutionError.prototype);\n }\n}\n\n/**\n * Resolve a `cdkl invoke-agentcore <target>` argument against the synthesized\n * stacks. Accepts the same target forms as every other command\n * (`Stack:LogicalId` / `Stack/Path/...` / bare in single-stack apps),\n * reusing {@link parseTarget} + the shared stack-matcher / cdk-path index.\n *\n * `imageContext` is optional: when the `ContainerUri` is an `Fn::Join`\n * (imported ECR repo / same-stack repo under `--from-cfn-stack`), the\n * caller threads resolved pseudo parameters + state so the URI reduces to\n * a concrete string. Literal / `Fn::Sub` URIs need no context.\n */\nexport function resolveAgentCoreTarget(\n target: string,\n stacks: StackInfo[],\n imageContext?: ImageResolutionContext\n): ResolvedAgentCoreRuntime {\n if (stacks.length === 0) {\n throw new AgentCoreResolutionError('No stacks found in the synthesized assembly.');\n }\n\n const parsed = parseTarget(target);\n const stack = pickStack(parsed, stacks);\n const resources = stack.template.Resources ?? {};\n\n const { logicalId, resource } = matchRuntime(parsed, target, stack, resources);\n\n if (resource.Type !== AGENTCORE_RUNTIME_TYPE) {\n throw new AgentCoreResolutionError(\n `Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not ${AGENTCORE_RUNTIME_TYPE}. ` +\n `${getEmbedConfig().cliName} invoke-agentcore only runs Bedrock AgentCore Runtime resources.`\n );\n }\n\n return extractRuntimeProperties(stack, logicalId, resource, resources, imageContext);\n}\n\n/**\n * Best-effort pick of the candidate stack a target lives in, BEFORE full\n * resolution — so the command can build a `--from-cfn-stack` image-resolution\n * context (state load + pseudo parameters) and thread it into\n * {@link resolveAgentCoreTarget} so a same-stack `AWS::ECR::Repository`\n * `Fn::Join` ContainerUri resolves to the deployed URI. Returns undefined when\n * the stack is ambiguous (multi-stack app, no prefix) — the caller proceeds\n * without a context and the resolver surfaces its own error if one is needed.\n * Mirrors `run-task`'s `pickCandidateStack`.\n */\nexport function pickAgentCoreCandidateStack(\n target: string,\n stacks: StackInfo[]\n): StackInfo | undefined {\n const parsed = parseTarget(target);\n if (parsed.stackPattern === null) {\n return stacks.length === 1 ? stacks[0] : undefined;\n }\n const matched = matchStacks(stacks, [parsed.stackPattern]);\n return matched.length === 1 ? matched[0] : undefined;\n}\n\n/**\n * Single-stack auto-detect: if the app has exactly one stack the user may\n * omit the stack prefix; otherwise an explicit stack pattern is required.\n * Mirrors the Lambda / ECS resolvers' behavior via the shared\n * stack-matcher.\n */\nfunction pickStack(parsed: ParsedTarget, stacks: StackInfo[]): StackInfo {\n if (parsed.stackPattern === null) {\n if (stacks.length === 1) return stacks[0]!;\n throw new AgentCoreResolutionError(\n `Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. ` +\n `Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). ` +\n `Available stacks: ${stacks.map((s) => s.stackName).join(', ')}.`\n );\n }\n const matched = matchStacks(stacks, [parsed.stackPattern]);\n if (matched.length === 0) {\n throw new AgentCoreResolutionError(\n `Stack '${parsed.stackPattern}' not found. ` +\n `Available stacks: ${stacks.map((s) => s.stackName).join(', ')}.`\n );\n }\n if (matched.length > 1) {\n throw new AgentCoreResolutionError(\n `Stack pattern '${parsed.stackPattern}' matched ${matched.length} stacks: ` +\n matched.map((s) => s.stackName).join(', ') +\n '. Use a more specific pattern.'\n );\n }\n return matched[0]!;\n}\n\n/** Resolve a parsed target to a single (logicalId, resource) pair. */\nfunction matchRuntime(\n parsed: ParsedTarget,\n target: string,\n stack: StackInfo,\n resources: Record<string, TemplateResource>\n): { logicalId: string; resource: TemplateResource } {\n if (parsed.isPath) {\n const index = buildCdkPathIndex(stack.template);\n const resolvedPaths = resolveCdkPathToLogicalIds(parsed.pathOrId, index);\n const runtimeMatches = resolvedPaths.filter(\n ({ logicalId }) => resources[logicalId]?.Type === AGENTCORE_RUNTIME_TYPE\n );\n if (runtimeMatches.length === 0) {\n throw notFoundError(target, stack, resources);\n }\n if (runtimeMatches.length > 1) {\n throw new AgentCoreResolutionError(\n `Target '${target}' matches ${runtimeMatches.length} AgentCore Runtimes in ${stack.stackName}: ` +\n runtimeMatches.map((m) => m.logicalId).join(', ') +\n '. Refine the path or use the stack:LogicalId form.'\n );\n }\n const m = runtimeMatches[0]!;\n return { logicalId: m.logicalId, resource: resources[m.logicalId]! };\n }\n\n const resource = resources[parsed.pathOrId];\n if (!resource) {\n throw notFoundError(target, stack, resources);\n }\n return { logicalId: parsed.pathOrId, resource };\n}\n\nfunction notFoundError(\n target: string,\n stack: StackInfo,\n resources: Record<string, TemplateResource>\n): AgentCoreResolutionError {\n const available = Object.entries(resources)\n .filter(([, r]) => r.Type === AGENTCORE_RUNTIME_TYPE)\n .map(([id]) => id);\n const hint =\n available.length > 0\n ? `Available AgentCore Runtimes in ${stack.stackName}: ${available.join(', ')}.`\n : `No ${AGENTCORE_RUNTIME_TYPE} resources found in ${stack.stackName}.`;\n return new AgentCoreResolutionError(`Target '${target}' not found. ${hint}`);\n}\n\n/** Pull the runtime properties `cdkl invoke-agentcore` cares about. */\nfunction extractRuntimeProperties(\n stack: StackInfo,\n logicalId: string,\n resource: TemplateResource,\n resources: Record<string, TemplateResource>,\n imageContext: ImageResolutionContext | undefined\n): ResolvedAgentCoreRuntime {\n const props = resource.Properties ?? {};\n\n const protocol = extractProtocol(props['ProtocolConfiguration'], logicalId, stack.stackName);\n const artifact = extractArtifact(\n props['AgentRuntimeArtifact'],\n logicalId,\n stack.stackName,\n resources,\n stack.region,\n imageContext\n );\n\n const environmentVariables =\n props['EnvironmentVariables'] &&\n typeof props['EnvironmentVariables'] === 'object' &&\n !Array.isArray(props['EnvironmentVariables'])\n ? (props['EnvironmentVariables'] as Record<string, unknown>)\n : {};\n\n const roleArn = typeof props['RoleArn'] === 'string' ? props['RoleArn'] : undefined;\n const jwtAuthorizer = extractJwtAuthorizer(props['AuthorizerConfiguration'], logicalId);\n\n return {\n stack,\n logicalId,\n resource,\n ...(artifact.kind === 'container'\n ? { containerUri: artifact.containerUri }\n : { codeArtifact: artifact.codeArtifact }),\n environmentVariables,\n protocol,\n ...(roleArn !== undefined && { roleArn }),\n ...(jwtAuthorizer !== undefined && { jwtAuthorizer }),\n };\n}\n\n/**\n * Extract a literal `CustomJWTAuthorizer` from `AuthorizerConfiguration`.\n * Returns undefined when there is no authorizer, or when `DiscoveryUrl` is\n * not a literal string (an unresolved intrinsic) — verification needs a\n * concrete URL to fetch, so an intrinsic is warn-and-skipped by the caller.\n */\nfunction extractJwtAuthorizer(\n authorizerConfig: unknown,\n logicalId: string\n): AgentCoreJwtAuthorizer | undefined {\n if (\n !authorizerConfig ||\n typeof authorizerConfig !== 'object' ||\n Array.isArray(authorizerConfig)\n ) {\n return undefined;\n }\n const jwt = (authorizerConfig as Record<string, unknown>)['CustomJWTAuthorizer'];\n if (!jwt || typeof jwt !== 'object' || Array.isArray(jwt)) return undefined;\n const cfg = jwt as Record<string, unknown>;\n\n const discoveryUrl = cfg['DiscoveryUrl'];\n if (typeof discoveryUrl !== 'string' || discoveryUrl.length === 0) {\n getLogger().warn(\n `AgentCore Runtime '${logicalId}' declares a CustomJWTAuthorizer whose DiscoveryUrl is not a literal string; ` +\n `${getEmbedConfig().cliName} invoke-agentcore cannot verify inbound JWTs against it and will skip auth.`\n );\n return undefined;\n }\n\n const toStringArray = (v: unknown): string[] | undefined =>\n Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : undefined;\n const allowedAudience = toStringArray(cfg['AllowedAudience']);\n const allowedClients = toStringArray(cfg['AllowedClients']);\n const allowedScopes = toStringArray(cfg['AllowedScopes']);\n const customClaims = extractCustomClaims(cfg['CustomClaims'], logicalId);\n\n return {\n discoveryUrl,\n ...(allowedAudience && allowedAudience.length > 0 && { allowedAudience }),\n ...(allowedClients && allowedClients.length > 0 && { allowedClients }),\n ...(allowedScopes && allowedScopes.length > 0 && { allowedScopes }),\n ...(customClaims && customClaims.length > 0 && { customClaims }),\n };\n}\n\n/**\n * Parse a `CustomJWTAuthorizer.CustomClaims[]` array into\n * {@link AgentCoreCustomClaim}s. The template shape (synthesized by the L2):\n *\n * ```\n * {\n * InboundTokenClaimName: <claim name>,\n * InboundTokenClaimValueType: 'STRING' | 'STRING_ARRAY',\n * AuthorizingClaimMatchValue: {\n * ClaimMatchOperator: 'EQUALS' | 'CONTAINS' | 'CONTAINS_ANY',\n * ClaimMatchValue: { MatchValueString?: ..., MatchValueStringList?: [...] }\n * }\n * }\n * ```\n *\n * Each entry that fails to parse (missing name / unknown type / unknown\n * operator / wrong value shape) is warn-and-skipped — the deployed runtime\n * would reject a token that violates ANY claim rule, so dropping a rule we\n * can't evaluate is the safer side (under-restrictive in `--no-verify-auth`\n * paths, fine elsewhere because the surviving rules still gate the token).\n */\nfunction extractCustomClaims(raw: unknown, logicalId: string): AgentCoreCustomClaim[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const out: AgentCoreCustomClaim[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue;\n const e = entry as Record<string, unknown>;\n const name = e['InboundTokenClaimName'];\n const valueType = e['InboundTokenClaimValueType'];\n const matchObj = e['AuthorizingClaimMatchValue'];\n if (typeof name !== 'string' || name.length === 0) continue;\n if (valueType !== 'STRING' && valueType !== 'STRING_ARRAY') {\n getLogger().warn(\n `AgentCore Runtime '${logicalId}' CustomClaims entry '${name}' has unsupported ` +\n `InboundTokenClaimValueType '${String(valueType)}' (expected STRING / STRING_ARRAY); skipping.`\n );\n continue;\n }\n if (!matchObj || typeof matchObj !== 'object' || Array.isArray(matchObj)) continue;\n const m = matchObj as Record<string, unknown>;\n const operator = m['ClaimMatchOperator'];\n const matchValue = m['ClaimMatchValue'];\n if (operator !== 'EQUALS' && operator !== 'CONTAINS' && operator !== 'CONTAINS_ANY') {\n getLogger().warn(\n `AgentCore Runtime '${logicalId}' CustomClaims entry '${name}' has unsupported ` +\n `ClaimMatchOperator '${String(operator)}' (expected EQUALS / CONTAINS / CONTAINS_ANY); skipping.`\n );\n continue;\n }\n if (!matchValue || typeof matchValue !== 'object' || Array.isArray(matchValue)) continue;\n const mv = matchValue as Record<string, unknown>;\n // STRING + EQUALS and STRING_ARRAY + CONTAINS use MatchValueString (single\n // string); STRING_ARRAY + CONTAINS_ANY uses MatchValueStringList (array).\n let value: string | string[] | undefined;\n if (operator === 'CONTAINS_ANY') {\n const list = mv['MatchValueStringList'];\n if (Array.isArray(list)) {\n value = list.filter((x): x is string => typeof x === 'string');\n if ((value as string[]).length === 0) value = undefined;\n }\n } else {\n const s = mv['MatchValueString'];\n if (typeof s === 'string' && s.length > 0) value = s;\n }\n if (value === undefined) {\n getLogger().warn(\n `AgentCore Runtime '${logicalId}' CustomClaims entry '${name}' has no usable ` +\n `MatchValueString / MatchValueStringList for operator ${operator}; skipping.`\n );\n continue;\n }\n out.push({ name, valueType, operator, value });\n }\n return out;\n}\n\n/**\n * Validate `ProtocolConfiguration`. Serves the four AgentCore protocols\n * (HTTP / MCP / A2A / AGUI); an unrecognized value hard-errors with the\n * supported list so the command never starts something it can't run.\n */\nfunction extractProtocol(value: unknown, logicalId: string, stackName: string): string {\n if (value === undefined || value === null) return AGENTCORE_HTTP_PROTOCOL;\n if (typeof value !== 'string') {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} has a non-string ProtocolConfiguration. ` +\n `${getEmbedConfig().cliName} invoke-agentcore supports the ${SUPPORTED_AGENTCORE_PROTOCOLS.join(' / ')} protocols.`\n );\n }\n if (!(SUPPORTED_AGENTCORE_PROTOCOLS as readonly string[]).includes(value)) {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} uses the ${value} protocol. ` +\n `${getEmbedConfig().cliName} invoke-agentcore supports the ${SUPPORTED_AGENTCORE_PROTOCOLS.join(' / ')} protocols.`\n );\n }\n return value;\n}\n\ntype ExtractedArtifact =\n | { kind: 'container'; containerUri: string }\n | { kind: 'code'; codeArtifact: AgentCoreCodeArtifact };\n\n/**\n * Resolve `AgentRuntimeArtifact` to either a container image URI or a code\n * artifact (managed runtime). A `ContainerConfiguration` yields the resolved\n * `ContainerUri`; a `CodeConfiguration` yields its `Runtime` / `EntryPoint` +\n * the cdk.out asset hash the command uses to locate the bundle source.\n */\nfunction extractArtifact(\n artifact: unknown,\n logicalId: string,\n stackName: string,\n resources: Record<string, TemplateResource>,\n region: string | undefined,\n imageContext: ImageResolutionContext | undefined\n): ExtractedArtifact {\n if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact)) {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} has no AgentRuntimeArtifact.`\n );\n }\n const art = artifact as Record<string, unknown>;\n\n if (art['CodeConfiguration'] && !art['ContainerConfiguration']) {\n return {\n kind: 'code',\n codeArtifact: extractCodeArtifact(art['CodeConfiguration'], logicalId, stackName),\n };\n }\n\n const container = art['ContainerConfiguration'];\n if (!container || typeof container !== 'object' || Array.isArray(container)) {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} has no ContainerConfiguration in its AgentRuntimeArtifact.`\n );\n }\n\n const uri = resolveImageUri(\n (container as Record<string, unknown>)['ContainerUri'],\n logicalId,\n stackName,\n resources,\n region,\n imageContext\n );\n if (uri === undefined) {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} has a ContainerConfiguration.ContainerUri that ${getEmbedConfig().cliName} invoke-agentcore cannot resolve. ` +\n `v1 resolves a literal image URI, an Fn::Sub asset URI (the fromAsset / Dockerfile path), an imported-ECR Fn::Join, ` +\n `and a same-stack AWS::ECR::Repository Fn::Join under --from-cfn-stack — build the agent as a fromAsset image, or pin a literal / imported ECR image URI.`\n );\n }\n return { kind: 'container', containerUri: uri };\n}\n\n/**\n * Extract a `CodeConfiguration` (managed-runtime) artifact. Reads `Runtime`,\n * `EntryPoint`, and the `Code.S3` location. `Code.S3.Prefix` must be a literal\n * string (the object key) — it doubles as the cdk.out file-asset hash for the\n * `fromCodeAsset` shape (`<hash>.zip`).\n *\n * - `Code.S3.Bucket` literal string → fromS3 bundle, `s3Source.bucket` set.\n * - `Code.S3.Bucket` intrinsic (`Ref` / `Fn::ImportValue` / `Fn::GetStackOutput`\n * / `Fn::Sub`) → fromS3 bundle, `s3Source.bucketIntrinsic` set (the command\n * resolves it against `--from-cfn-stack` state via the same machinery env\n * vars use).\n * - `Code.S3.Bucket` missing → fromCodeAsset shape (the staging bucket renders\n * as an `Fn::Sub` intrinsic for fromCodeAsset, but the cdk.out lookup\n * short-circuits that — no `s3Source` needed).\n *\n * A non-literal `Code.S3.Prefix` (an unresolved intrinsic) hard-errors.\n */\nfunction extractCodeArtifact(\n codeConfig: unknown,\n logicalId: string,\n stackName: string\n): AgentCoreCodeArtifact {\n const cfg =\n codeConfig && typeof codeConfig === 'object' && !Array.isArray(codeConfig)\n ? (codeConfig as Record<string, unknown>)\n : {};\n\n const runtime = cfg['Runtime'];\n if (typeof runtime !== 'string' || runtime.length === 0) {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} has a CodeConfiguration with no string Runtime.`\n );\n }\n\n const entryPointRaw = cfg['EntryPoint'];\n const entryPoint = Array.isArray(entryPointRaw)\n ? entryPointRaw.filter((x): x is string => typeof x === 'string')\n : [];\n if (entryPoint.length === 0) {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} has a CodeConfiguration with no EntryPoint.`\n );\n }\n\n const s3 =\n cfg['Code'] && typeof cfg['Code'] === 'object'\n ? (cfg['Code'] as Record<string, unknown>)['S3']\n : undefined;\n const s3Obj =\n s3 && typeof s3 === 'object' && !Array.isArray(s3) ? (s3 as Record<string, unknown>) : {};\n const prefix = s3Obj['Prefix'];\n if (typeof prefix !== 'string' || prefix.length === 0) {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} has a CodeConfiguration whose Code.S3.Prefix is not a literal string. ` +\n `${getEmbedConfig().cliName} invoke-agentcore needs a literal object key — re-synthesize a fromCodeAsset bundle, ` +\n `or pass a literal bucket + key for a fromS3 bundle.`\n );\n }\n\n // Prefix is `<assetHash>.zip` (possibly with a key prefix) → the cdk.out\n // file-asset hash the command looks up to find a fromCodeAsset bundle source.\n const codeAssetHash = prefix.replace(/^.*\\//, '').replace(/\\.zip$/, '');\n\n // A literal Bucket means a fromS3 bundle. An intrinsic Bucket — one of\n // {Ref, Fn::ImportValue, Fn::GetStackOutput} — is also a fromS3 bundle whose\n // bucket name we resolve against `--from-cfn-stack` state at command time.\n // The CDK staging bucket of a fromCodeAsset renders as `{Fn::Sub: \"...\"}`\n // and is INTENTIONALLY skipped here so fromCodeAsset still routes through\n // the cdk.out asset path.\n const bucket = s3Obj['Bucket'];\n const versionId = s3Obj['VersionId'];\n const versionIdField = typeof versionId === 'string' && versionId.length > 0 ? { versionId } : {};\n let s3Source: AgentCoreCodeArtifact['s3Source'] | undefined;\n if (typeof bucket === 'string' && bucket.length > 0) {\n s3Source = { bucket, key: prefix, ...versionIdField };\n } else if (isFromS3BucketIntrinsic(bucket)) {\n s3Source = { bucketIntrinsic: bucket, key: prefix, ...versionIdField };\n }\n\n return { runtime, entryPoint, codeAssetHash, ...(s3Source && { s3Source }) };\n}\n\n/**\n * Whitelist the intrinsic shapes the command can resolve for a fromS3\n * `Code.S3.Bucket` against `--from-cfn-stack` state — `Ref` (same-stack\n * resource), `Fn::ImportValue` (CloudFormation export), `Fn::GetStackOutput`\n * (cdk-local cross-stack output). Crucially excludes `Fn::Sub`, which is the\n * fromCodeAsset staging-bucket shape: that one stays unmarked so fromCodeAsset\n * routes through the cdk.out asset path unchanged.\n */\nfunction isFromS3BucketIntrinsic(value: unknown): value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return false;\n const obj = value as Record<string, unknown>;\n return 'Ref' in obj || 'Fn::ImportValue' in obj || 'Fn::GetStackOutput' in obj;\n}\n\n/**\n * Resolve a `ContainerUri` value to a string. Handles a literal string,\n * an `Fn::Sub` (the template returned verbatim — `${AWS::*}` placeholders\n * are kept for asset-hash matching / later ECR substitution), and the\n * canonical CDK `Fn::Join` ECR shape via the shared {@link intrinsic-image}\n * resolver. A same-stack `AWS::ECR::Repository` Fn::Join without\n * `--from-cfn-stack` throws an `AgentCoreResolutionError` pointing the user\n * at the right flag (mirroring `cdkl run-task`'s shape). Returns undefined\n * when none of the supported shapes apply.\n */\nfunction resolveImageUri(\n value: unknown,\n logicalId: string,\n stackName: string,\n resources: Record<string, TemplateResource>,\n region: string | undefined,\n imageContext: ImageResolutionContext | undefined\n): string | undefined {\n if (typeof value === 'string' && value.length > 0) return value;\n if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;\n\n const obj = value as Record<string, unknown>;\n\n const sub = obj['Fn::Sub'];\n if (typeof sub === 'string' && sub.length > 0) {\n return imageContext ? substituteImagePlaceholders(sub, resources, imageContext) : sub;\n }\n if (Array.isArray(sub) && typeof sub[0] === 'string') {\n return imageContext ? substituteImagePlaceholders(sub[0], resources, imageContext) : sub[0];\n }\n\n if ('Fn::Join' in obj) {\n const context: ImageResolutionContext | undefined =\n imageContext ??\n (() => {\n const pseudoParameters = derivePseudoParametersFromRegion(region);\n return pseudoParameters ? { pseudoParameters } : undefined;\n })();\n const joinResolved = tryResolveImageFnJoin(value, resources, context);\n if (joinResolved.kind === 'resolved') return joinResolved.uri;\n // Mirror `cdkl run-task`'s shape: when the Fn::Join references a\n // same-stack AWS::ECR::Repository but no state has been loaded, point the\n // user at `--from-cfn-stack` rather than the coarse \"cannot resolve\" we\n // fall through to for genuinely unsupported intrinsics.\n if (joinResolved.kind === 'needs-state') {\n throw new AgentCoreResolutionError(\n `AgentCore Runtime '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. ` +\n `${getEmbedConfig().cliName} invoke-agentcore cannot resolve the repository URI without state — ` +\n 'pass --from-cfn-stack to load the deployed stack state, ' +\n 'build via Runtime.fromAsset, or pin a literal / imported ECR image URI.'\n );\n }\n }\n\n return undefined;\n}\n","/**\n * Shared resolver for CFn intrinsic shapes that show up wherever an API\n * Gateway construct references a Lambda function ARN — both\n * `Integration.Uri` (`route-discovery.ts`) and `AuthorizerUri`\n * (`authorizer-resolver.ts`). CDK 2.x synthesizes the same canonical\n * shapes across both call sites:\n *\n * 1. `{ Ref: <LambdaLogicalId> }` — rare, but accepted.\n * 2. `{ 'Fn::GetAtt': [<LambdaLogicalId>, 'Arn'] }` — common HTTP API\n * shape.\n * 3. **REST v1 / HTTP API v2 invoke-ARN wrap**: `{ 'Fn::Join': ['',\n * ['arn:', { Ref: 'AWS::Partition' }, ':apigateway:', { Ref:\n * 'AWS::Region' }, ':lambda:path/2015-03-31/functions/', { 'Fn::GetAtt':\n * [<LambdaLogicalId>, 'Arn'] }, '/invocations']] }` — the shape both\n * `apigateway.LambdaIntegration({proxy: true})` and\n * `apigatewayv2-authorizers.HttpLambdaAuthorizer` synthesize.\n * 4. **`Fn::Sub` invoke-ARN** (issue #286 Gaps 3 / 4): hand-written /\n * non-canonical CDK constructs may emit `Fn::Sub` instead of\n * `Fn::Join`, e.g.\n * `{ Fn::Sub: 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations' }`\n * (1-arg form, AWS-docs canonical) or\n * `{ Fn::Sub: ['arn:...:${MyLambdaArn}/invocations', { MyLambdaArn:\n * {Fn::GetAtt: [<LambdaLogicalId>, 'Arn']} }] }` (2-arg form, what\n * CDK's `Fn.sub(template, vars)` synthesizes).\n *\n * Originally each call site (`route-discovery.ts` and\n * `authorizer-resolver.ts`) had its own near-identical ad-hoc resolver\n * that recognized cases 1-3; this module is the consolidated resolver\n * adding case 4 (Gaps 3 / 4 of #286). Same extraction pattern as PR #293\n * (`tryResolveImageFnJoin` in `src/local/intrinsic-image.ts`).\n *\n * The resolver is **pure-functional and synchronous** — it has no AWS\n * SDK dependencies, no deploy-state coupling, and returns a discriminated\n * union so each caller can wrap the unsupported case with its own error\n * class (`Error` for route-discovery, `RouteDiscoveryError` for\n * authorizer-resolver).\n */\n\n/**\n * Outcome of attempting to resolve a Lambda ARN intrinsic. Discriminated\n * so the caller can route the resolved case to its existing happy path\n * and the unsupported case to its existing error path with the supplied\n * `detail` appended.\n */\nexport type LambdaArnResolveOutcome =\n | { kind: 'resolved'; logicalId: string }\n | { kind: 'unsupported'; detail: string };\n\n/**\n * Marker substring that AWS's Lambda-integration invoke ARN always\n * contains. Matches the `:lambda:path/2015-03-31/functions/` segment AWS\n * documents at\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-lambda-proxy-integrations.html.\n * Used as the load-bearing signal that the surrounding `Fn::Join` /\n * `Fn::Sub` template is actually an invoke-ARN wrapper rather than an\n * unrelated intrinsic that happens to land on the same field.\n */\nconst INVOKE_ARN_MARKER = ':lambda:path/2015-03-31/functions/';\n\n/**\n * Resolve a Lambda ARN intrinsic to the Lambda's logical ID. Accepts\n * every CDK 2.x-canonical shape (`Ref` / `Fn::GetAtt: [..., 'Arn']` /\n * `Fn::Join` invoke-ARN wrapper / `Fn::Sub` invoke-ARN wrapper).\n *\n * Returns `{kind: 'resolved', logicalId}` on success. Returns\n * `{kind: 'unsupported', detail}` for any other shape, where `detail`\n * names the surface-level problem (the caller's location prefix +\n * `shortJson` rendering is layered on top). Never throws.\n */\nexport function resolveLambdaArnIntrinsic(value: unknown): LambdaArnResolveOutcome {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return { kind: 'unsupported', detail: 'expected an object intrinsic' };\n }\n\n const obj = value as Record<string, unknown>;\n\n // Case 1: { Ref: <LambdaLogicalId> }\n if ('Ref' in obj && typeof obj['Ref'] === 'string') {\n return { kind: 'resolved', logicalId: obj['Ref'] };\n }\n\n // Case 2: { Fn::GetAtt: [<LambdaLogicalId>, 'Arn'] }\n if ('Fn::GetAtt' in obj) {\n const arg = obj['Fn::GetAtt'];\n if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === 'string' && arg[1] === 'Arn') {\n return { kind: 'resolved', logicalId: arg[0] };\n }\n return { kind: 'unsupported', detail: \"Fn::GetAtt must be [<LambdaLogicalId>, 'Arn']\" };\n }\n\n // Case 3: Fn::Join invoke-ARN wrapper.\n if ('Fn::Join' in obj) {\n const join = obj['Fn::Join'];\n if (Array.isArray(join) && join.length === 2 && Array.isArray(join[1])) {\n // First element is the separator; the second is the parts list.\n // `parts.join('')` should look like the invoke-ARN template; we\n // verify by looking for the AWS-documented marker in any string\n // entry, then pluck the GetAtt logical ID out of the parts list.\n const parts = join[1] as unknown[];\n const literalParts = parts.filter((p): p is string => typeof p === 'string').join('');\n if (literalParts.includes(INVOKE_ARN_MARKER)) {\n for (const p of parts) {\n if (p && typeof p === 'object' && !Array.isArray(p)) {\n const inner = p as Record<string, unknown>;\n const arg = inner['Fn::GetAtt'];\n if (\n Array.isArray(arg) &&\n arg.length === 2 &&\n typeof arg[0] === 'string' &&\n arg[1] === 'Arn'\n ) {\n return { kind: 'resolved', logicalId: arg[0] };\n }\n }\n }\n return {\n kind: 'unsupported',\n detail:\n \"Fn::Join invoke-ARN wrapper does not contain a { Fn::GetAtt: [<LambdaLogicalId>, 'Arn'] } element\",\n };\n }\n }\n return { kind: 'unsupported', detail: 'Fn::Join does not look like an invoke-ARN wrapper' };\n }\n\n // Case 4: Fn::Sub invoke-ARN wrapper (Gaps 3 / 4 of #286).\n if ('Fn::Sub' in obj) {\n return resolveFnSubInvokeArn(obj['Fn::Sub']);\n }\n\n return {\n kind: 'unsupported',\n detail:\n \"expected { Ref: <LambdaLogicalId> }, { 'Fn::GetAtt': [<LambdaLogicalId>, 'Arn'] }, the REST v1 invoke-ARN Fn::Join wrapper, or the Fn::Sub invoke-ARN wrapper\",\n };\n}\n\n/**\n * Match the canonical Fn::Sub invoke-ARN shape:\n *\n * - 1-arg form (AWS-docs canonical):\n * `{ Fn::Sub: 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations' }`\n * The `${MyLambda.Arn}` placeholder names the Lambda logical id\n * directly inside the template.\n *\n * - 2-arg form (what CDK's `Fn.sub(template, vars)` synthesizes):\n * `{ Fn::Sub: ['arn:...:${MyLambdaArn}/invocations', { MyLambdaArn:\n * {Fn::GetAtt: [<LambdaLogicalId>, 'Arn']} }] }`\n * The var map's value is the `Fn::GetAtt: [..., 'Arn']` shape we\n * can resolve.\n *\n * Both forms require the invoke-ARN marker substring to keep the\n * resolver narrow — a Fn::Sub against an arbitrary template is NOT\n * accepted (matches the pre-PR rejection message intent).\n */\nfunction resolveFnSubInvokeArn(arg: unknown): LambdaArnResolveOutcome {\n let template: string | undefined;\n let varMap: Record<string, unknown> | undefined;\n\n if (typeof arg === 'string') {\n template = arg;\n } else if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === 'string') {\n template = arg[0];\n if (arg[1] && typeof arg[1] === 'object' && !Array.isArray(arg[1])) {\n varMap = arg[1] as Record<string, unknown>;\n } else {\n return {\n kind: 'unsupported',\n detail: 'Fn::Sub second argument must be a { varName: intrinsic } map',\n };\n }\n } else {\n return {\n kind: 'unsupported',\n detail:\n 'Fn::Sub must be either a template string or [template, { var: intrinsic }] (got non-canonical arg)',\n };\n }\n\n if (!template.includes(INVOKE_ARN_MARKER)) {\n return {\n kind: 'unsupported',\n detail: 'Fn::Sub template does not look like an invoke-ARN wrapper',\n };\n }\n\n // Find the ${...} placeholder that names the Lambda function ARN.\n // Two sub-shapes:\n // (a) `${LogicalId.Arn}` — 1-arg form references the resource directly\n // via CFn's implicit `Fn::GetAtt`.\n // (b) `${VarName}` — 2-arg form references a key of varMap; the\n // value must be `Fn::GetAtt: [<LambdaLogicalId>, 'Arn']`.\n //\n // The invoke-ARN template only has one placeholder in the\n // `:functions/<placeholder>/invocations` slot; AWS's documented form\n // also uses `${AWS::Region}` / `${AWS::Partition}` pseudo-parameter\n // refs in earlier slots, but those are NOT the lambda reference and\n // do not look like `${X.Arn}` or appear in the var map.\n const placeholderRe = /\\$\\{([^}]+)\\}/g;\n let match: RegExpExecArray | null;\n while ((match = placeholderRe.exec(template)) !== null) {\n const key = match[1]!;\n\n // Pseudo-parameter refs (`AWS::Region` / `AWS::Partition` / etc.) —\n // skip; these aren't the lambda placeholder.\n if (key.startsWith('AWS::')) continue;\n\n // Sub-shape (a): `${LogicalId.Arn}`.\n const dot = key.indexOf('.');\n if (dot > 0 && dot < key.length - 1) {\n const logicalId = key.slice(0, dot);\n const attr = key.slice(dot + 1);\n if (attr === 'Arn') {\n return { kind: 'resolved', logicalId };\n }\n // `${X.SomeOtherAttr}` doesn't look like a Lambda ARN reference;\n // skip to the next placeholder.\n continue;\n }\n\n // Sub-shape (b): `${VarName}` against the var map.\n if (varMap && key in varMap) {\n const v = varMap[key];\n if (v && typeof v === 'object' && !Array.isArray(v)) {\n const inner = v as Record<string, unknown>;\n const getAtt = inner['Fn::GetAtt'];\n if (\n Array.isArray(getAtt) &&\n getAtt.length === 2 &&\n typeof getAtt[0] === 'string' &&\n getAtt[1] === 'Arn'\n ) {\n return { kind: 'resolved', logicalId: getAtt[0] };\n }\n }\n // Var map entry didn't resolve to a `Fn::GetAtt: [..., 'Arn']`\n // shape; try the next placeholder before giving up.\n continue;\n }\n }\n\n return {\n kind: 'unsupported',\n detail:\n \"Fn::Sub invoke-ARN template did not contain a recognizable ${LogicalId.Arn} placeholder or matching var-map entry with Fn::GetAtt: [..., 'Arn']\",\n };\n}\n","/**\n * If `value` is a `{ Ref: <string> }` intrinsic, return the referenced\n * logical ID. Otherwise return `null`.\n *\n * Shared across the `src/local/*` resolvers (route discovery, authorizer\n * resolution, stage attachment) so future intrinsic-shape extensions\n * (e.g. accepting `Fn::Sub`-bound Refs in REST v1 ResourceId / ParentId)\n * land in one place instead of three.\n */\nexport function pickRefLogicalId(value: unknown): string | null {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const ref = (value as Record<string, unknown>)['Ref'];\n if (typeof ref === 'string') return ref;\n }\n return null;\n}\n","/**\n * Per-subtype dispatcher for HTTP API v2 service integrations\n * (`IntegrationType: AWS_PROXY` + `IntegrationSubtype: <Service>-<Action>`).\n *\n * Full AWS-documented subtype list (NOT exhaustive — see AWS docs at\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html\n * for the complete reference, which also covers Lambda-Invoke, the\n * DynamoDB-* family, SNS-Publish, AppConfig-StartConfigurationSession,\n * SQS-SendMessageBatch / Kinesis-PutRecords, etc.).\n *\n * Subtypes cdk-local currently bundles SDK clients for:\n * - EventBridge-PutEvents\n * - SQS-SendMessage / SQS-ReceiveMessage / SQS-DeleteMessage / SQS-PurgeQueue\n * - Kinesis-PutRecord\n * - StepFunctions-StartExecution / StartSyncExecution / StopExecution\n * - AppConfig-GetConfiguration (recognized but returns 501 — the\n * `@aws-sdk/client-appconfig` package is not yet bundled)\n *\n * Unrecognized subtypes (including AWS-documented entries cdk-local does not\n * yet implement, and outright typos) fall back to the deferred-501 path\n * in `route-discovery.ts`, surfacing a clean HTTP 501 at request time\n * rather than aborting boot.\n *\n * Each subtype maps to ONE AWS SDK call. The `RequestParameters` map\n * carries the SDK input (already resolved to strings by\n * `parameter-mapping.ts`); per-subtype adapters convert it to the\n * shape the SDK's typed `*Command` constructor expects.\n *\n * `Region` is special-cased: AWS docs list it as an optional parameter\n * on every subtype to override the SDK client's default region. When\n * resolved to a non-empty string, the per-subtype adapter passes it\n * to the SDK client constructor.\n *\n * Authentication: SDK calls run under the dev's local AWS credential\n * chain (same chain as `cdkl invoke --assume-role` v1) — no\n * separate `--service-integration-role` flag in this PR. The dev's\n * permissions therefore control what the local route can reach,\n * matching the safe-by-default precedent set by sigv4-verify.ts.\n *\n * Response shape: each adapter returns\n * `{ statusCode, body, headers }`\n * which the HTTP server passes through verbatim. SDK errors surface as\n * HTTP 4xx (client errors — e.g. NonExistentQueue, ValidationException)\n * or HTTP 5xx (service errors). The default content-type is\n * `application/json`; per-subtype overlays are applied via the\n * `IntegrationResponseParameters` overlay applied separately by the\n * server.\n */\n\nimport type * as EventBridgeNs from '@aws-sdk/client-eventbridge';\nimport type * as KinesisNs from '@aws-sdk/client-kinesis';\nimport type * as SfnNs from '@aws-sdk/client-sfn';\nimport type * as SqsNs from '@aws-sdk/client-sqs';\nimport { stringifyValue } from '../utils/stringify.js';\nimport { getLogger } from '../utils/logger.js';\nimport { getEmbedConfig } from './embed-config.js';\n\nconst logger = getLogger();\n\n/**\n * Resolved service-integration call descriptor — emitted by the route\n * discovery layer, consumed by the dispatcher.\n */\nexport interface ServiceIntegrationSpec {\n /** Canonical subtype, e.g. `'SQS-SendMessage'`. */\n subtype: SupportedSubtype;\n /** The raw `RequestParameters` map from the CFn template. */\n requestParameters: Readonly<Record<string, unknown>>;\n /** Optional per-status-code `ResponseParameters` from the CFn template. */\n responseParameters?: Readonly<Record<string, Readonly<Record<string, string>>>>;\n}\n\n/**\n * SDK-call outcome before response-parameter overlay.\n */\nexport interface ServiceIntegrationResult {\n statusCode: number;\n body: string;\n headers: Record<string, string>;\n}\n\nexport type SupportedSubtype =\n | 'EventBridge-PutEvents'\n | 'SQS-SendMessage'\n | 'SQS-ReceiveMessage'\n | 'SQS-DeleteMessage'\n | 'SQS-PurgeQueue'\n | 'Kinesis-PutRecord'\n | 'StepFunctions-StartExecution'\n | 'StepFunctions-StartSyncExecution'\n | 'StepFunctions-StopExecution'\n | 'AppConfig-GetConfiguration';\n\n/**\n * Full list of subtypes cdk-local recognizes as supported. Mirrors AWS docs.\n */\nexport const SUPPORTED_SUBTYPES: readonly SupportedSubtype[] = [\n 'EventBridge-PutEvents',\n 'SQS-SendMessage',\n 'SQS-ReceiveMessage',\n 'SQS-DeleteMessage',\n 'SQS-PurgeQueue',\n 'Kinesis-PutRecord',\n 'StepFunctions-StartExecution',\n 'StepFunctions-StartSyncExecution',\n 'StepFunctions-StopExecution',\n 'AppConfig-GetConfiguration',\n];\n\n/**\n * Type guard: is the string an AWS-recognized service-integration subtype?\n *\n * Used by route discovery to classify routes — recognized subtypes go\n * through dispatch, anything else (typo, future-AWS-subtype-not-yet-supported)\n * falls back to deferred-501.\n */\nexport function isSupportedSubtype(value: unknown): value is SupportedSubtype {\n return typeof value === 'string' && (SUPPORTED_SUBTYPES as readonly string[]).includes(value);\n}\n\n/**\n * Lazy-loaded SDK clients keyed by `<service>:<region>`. SDK packages\n * are heavyweight (~5-10 MB each); per-process per-region caching\n * avoids re-instantiating the AWS Signer + middleware stack on every\n * request.\n */\nconst clientCache = new Map<string, unknown>();\n\nasync function getClient(service: string, region: string): Promise<unknown> {\n const key = `${service}:${region}`;\n const cached = clientCache.get(key);\n if (cached) return cached;\n let client: unknown;\n switch (service) {\n case 'sqs': {\n const mod = await import('@aws-sdk/client-sqs');\n client = new mod.SQSClient({ region });\n break;\n }\n case 'sns': {\n const mod = await import('@aws-sdk/client-sns');\n client = new mod.SNSClient({ region });\n break;\n }\n case 'eventbridge': {\n const mod = await import('@aws-sdk/client-eventbridge');\n client = new mod.EventBridgeClient({ region });\n break;\n }\n case 'kinesis': {\n const mod = await import('@aws-sdk/client-kinesis');\n client = new mod.KinesisClient({ region });\n break;\n }\n case 'sfn': {\n const mod = await import('@aws-sdk/client-sfn');\n client = new mod.SFNClient({ region });\n break;\n }\n case 'ssm': {\n const mod = await import('@aws-sdk/client-ssm');\n client = new mod.SSMClient({ region });\n break;\n }\n default:\n throw new Error(`unknown service '${service}'`);\n }\n clientCache.set(key, client);\n return client;\n}\n\n/**\n * Internal test hook — drop all cached SDK clients so unit tests can\n * reset module-scoped state between cases. NOT exported via the public\n * `index.ts`; used only by `tests/unit/local/httpv2-service-integration.test.ts`.\n */\nexport function _resetClientCacheForTest(): void {\n clientCache.clear();\n}\n\n/**\n * Dispatch a service integration: build the SDK input from the\n * pre-resolved parameter map, invoke the SDK, translate the response\n * to HTTP shape.\n *\n * `defaultRegion` is the cdk-local process's default AWS region (from\n * `AWS_REGION` / profile / `--region`). When the resolved parameter\n * map includes a non-empty `Region`, that value overrides the default\n * for this single call — matches AWS API Gateway behavior.\n *\n * Returns a `ServiceIntegrationResult` for the HTTP server to write\n * to the client. SDK-level errors are caught and translated to\n * HTTP 4xx / 5xx — never thrown.\n */\nexport async function dispatchServiceIntegration(\n subtype: SupportedSubtype,\n resolvedParameters: Readonly<Record<string, string>>,\n defaultRegion: string\n): Promise<ServiceIntegrationResult> {\n // Extract the optional Region parameter — it's documented on every\n // subtype as the SDK client region override.\n const region = (resolvedParameters['Region'] || defaultRegion).trim();\n if (!region) {\n return errorResponse(\n 400,\n \"No AWS region configured. Set --region, AWS_REGION, or pass a 'Region' RequestParameter.\"\n );\n }\n\n try {\n switch (subtype) {\n case 'EventBridge-PutEvents':\n return await dispatchEventBridgePutEvents(resolvedParameters, region);\n case 'SQS-SendMessage':\n return await dispatchSqsSendMessage(resolvedParameters, region);\n case 'SQS-ReceiveMessage':\n return await dispatchSqsReceiveMessage(resolvedParameters, region);\n case 'SQS-DeleteMessage':\n return await dispatchSqsDeleteMessage(resolvedParameters, region);\n case 'SQS-PurgeQueue':\n return await dispatchSqsPurgeQueue(resolvedParameters, region);\n case 'Kinesis-PutRecord':\n return await dispatchKinesisPutRecord(resolvedParameters, region);\n case 'StepFunctions-StartExecution':\n return await dispatchSfnStartExecution(resolvedParameters, region);\n case 'StepFunctions-StartSyncExecution':\n return await dispatchSfnStartSyncExecution(resolvedParameters, region);\n case 'StepFunctions-StopExecution':\n return await dispatchSfnStopExecution(resolvedParameters, region);\n case 'AppConfig-GetConfiguration':\n return await dispatchAppConfigGetConfiguration(resolvedParameters, region);\n }\n } catch (err) {\n return translateSdkError(subtype, err);\n }\n}\n\n// ---------------------------------------------------------------------\n// Per-subtype adapters\n// ---------------------------------------------------------------------\n\nasync function dispatchEventBridgePutEvents(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['Detail', 'DetailType', 'Source']);\n const mod = await import('@aws-sdk/client-eventbridge');\n const client = (await getClient('eventbridge', region)) as EventBridgeNs.EventBridgeClient;\n const entry: Record<string, unknown> = {\n Detail: params['Detail'],\n DetailType: params['DetailType'],\n Source: params['Source'],\n };\n if (params['Time']) entry['Time'] = new Date(params['Time']);\n if (params['EventBusName']) entry['EventBusName'] = params['EventBusName'];\n if (params['Resources']) entry['Resources'] = splitCsv(params['Resources']);\n if (params['TraceHeader']) entry['TraceHeader'] = params['TraceHeader'];\n const response = await client.send(\n new mod.PutEventsCommand({ Entries: [entry as EventBridgeNs.PutEventsRequestEntry] })\n );\n return okJson(response);\n}\n\nasync function dispatchSqsSendMessage(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['QueueUrl', 'MessageBody']);\n const mod = await import('@aws-sdk/client-sqs');\n const client = (await getClient('sqs', region)) as SqsNs.SQSClient;\n const input: Record<string, unknown> = {\n QueueUrl: params['QueueUrl'],\n MessageBody: params['MessageBody'],\n };\n if (params['DelaySeconds']) input['DelaySeconds'] = Number(params['DelaySeconds']);\n if (params['MessageDeduplicationId'])\n input['MessageDeduplicationId'] = params['MessageDeduplicationId'];\n if (params['MessageGroupId']) input['MessageGroupId'] = params['MessageGroupId'];\n if (params['MessageAttributes']) {\n input['MessageAttributes'] = parseJsonOrEmpty(params['MessageAttributes']);\n }\n if (params['MessageSystemAttributes']) {\n input['MessageSystemAttributes'] = parseJsonOrEmpty(params['MessageSystemAttributes']);\n }\n const response = await client.send(\n new mod.SendMessageCommand(input as unknown as SqsNs.SendMessageCommandInput)\n );\n return okJson(response);\n}\n\nasync function dispatchSqsReceiveMessage(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['QueueUrl']);\n const mod = await import('@aws-sdk/client-sqs');\n const client = (await getClient('sqs', region)) as SqsNs.SQSClient;\n const input: Record<string, unknown> = { QueueUrl: params['QueueUrl'] };\n if (params['AttributeNames']) input['AttributeNames'] = splitCsv(params['AttributeNames']);\n if (params['MaxNumberOfMessages'])\n input['MaxNumberOfMessages'] = Number(params['MaxNumberOfMessages']);\n if (params['MessageAttributeNames'])\n input['MessageAttributeNames'] = splitCsv(params['MessageAttributeNames']);\n if (params['ReceiveRequestAttemptId'])\n input['ReceiveRequestAttemptId'] = params['ReceiveRequestAttemptId'];\n if (params['VisibilityTimeout']) input['VisibilityTimeout'] = Number(params['VisibilityTimeout']);\n if (params['WaitTimeSeconds']) input['WaitTimeSeconds'] = Number(params['WaitTimeSeconds']);\n const response = await client.send(\n new mod.ReceiveMessageCommand(input as unknown as SqsNs.ReceiveMessageCommandInput)\n );\n return okJson(response);\n}\n\nasync function dispatchSqsDeleteMessage(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['QueueUrl', 'ReceiptHandle']);\n const mod = await import('@aws-sdk/client-sqs');\n const client = (await getClient('sqs', region)) as SqsNs.SQSClient;\n const response = await client.send(\n new mod.DeleteMessageCommand({\n QueueUrl: params['QueueUrl'],\n ReceiptHandle: params['ReceiptHandle'],\n })\n );\n return okJson(response);\n}\n\nasync function dispatchSqsPurgeQueue(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['QueueUrl']);\n const mod = await import('@aws-sdk/client-sqs');\n const client = (await getClient('sqs', region)) as SqsNs.SQSClient;\n const response = await client.send(new mod.PurgeQueueCommand({ QueueUrl: params['QueueUrl'] }));\n return okJson(response);\n}\n\nasync function dispatchKinesisPutRecord(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['StreamName', 'Data', 'PartitionKey']);\n const mod = await import('@aws-sdk/client-kinesis');\n const client = (await getClient('kinesis', region)) as KinesisNs.KinesisClient;\n // Data is documented as base64 in the AWS PutRecord API; HTTP API\n // v2 service integrations pass it through as-is (a string already),\n // and the SDK accepts Uint8Array. Best-effort: if the resolved value\n // is already base64-shaped (no whitespace + only base64 alphabet),\n // decode it; otherwise treat the raw string as UTF-8 bytes.\n const dataBytes = decodeBase64OrUtf8(params['Data'] ?? '');\n const input: Record<string, unknown> = {\n StreamName: params['StreamName'],\n Data: dataBytes,\n PartitionKey: params['PartitionKey'],\n };\n if (params['SequenceNumberForOrdering'])\n input['SequenceNumberForOrdering'] = params['SequenceNumberForOrdering'];\n if (params['ExplicitHashKey']) input['ExplicitHashKey'] = params['ExplicitHashKey'];\n const response = await client.send(\n new mod.PutRecordCommand(input as unknown as KinesisNs.PutRecordInput)\n );\n return okJson(response);\n}\n\nasync function dispatchSfnStartExecution(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['StateMachineArn']);\n const mod = await import('@aws-sdk/client-sfn');\n const client = (await getClient('sfn', region)) as SfnNs.SFNClient;\n const input: Record<string, unknown> = { stateMachineArn: params['StateMachineArn'] };\n if (params['Name']) input['name'] = params['Name'];\n if (params['Input']) input['input'] = params['Input'];\n const response = await client.send(\n new mod.StartExecutionCommand(input as unknown as SfnNs.StartExecutionInput)\n );\n return okJson(response);\n}\n\nasync function dispatchSfnStartSyncExecution(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['StateMachineArn']);\n const mod = await import('@aws-sdk/client-sfn');\n const client = (await getClient('sfn', region)) as SfnNs.SFNClient;\n const input: Record<string, unknown> = { stateMachineArn: params['StateMachineArn'] };\n if (params['Name']) input['name'] = params['Name'];\n if (params['Input']) input['input'] = params['Input'];\n if (params['TraceHeader']) input['traceHeader'] = params['TraceHeader'];\n const response = await client.send(\n new mod.StartSyncExecutionCommand(input as unknown as SfnNs.StartSyncExecutionInput)\n );\n return okJson(response);\n}\n\nasync function dispatchSfnStopExecution(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n requireParams(params, ['ExecutionArn']);\n const mod = await import('@aws-sdk/client-sfn');\n const client = (await getClient('sfn', region)) as SfnNs.SFNClient;\n const input: Record<string, unknown> = { executionArn: params['ExecutionArn'] };\n if (params['Cause']) input['cause'] = params['Cause'];\n if (params['Error']) input['error'] = params['Error'];\n const response = await client.send(\n new mod.StopExecutionCommand(input as unknown as SfnNs.StopExecutionInput)\n );\n return okJson(response);\n}\n\nasync function dispatchAppConfigGetConfiguration(\n params: Record<string, string>,\n region: string\n): Promise<ServiceIntegrationResult> {\n // The AWS docs map AppConfig-GetConfiguration to the LEGACY\n // appconfig:GetConfiguration call (deprecated by AWS in favor of\n // GetLatestConfiguration via AppConfigData). The legacy operation\n // lives in `@aws-sdk/client-appconfig`, not appconfigdata. We do\n // NOT carry that client in package.json today (not used elsewhere\n // in cdk-local) — surface a clear \"package not present\" error rather\n // than depending on it for a single subtype. Adding the dep is a\n // follow-up if real usage emerges.\n void params;\n void region;\n return errorResponse(\n 501,\n `AppConfig-GetConfiguration is recognized but ${getEmbedConfig().productName} does not yet bundle @aws-sdk/client-appconfig. Use the deployed API for this subtype, or open an issue if you need local emulation.`\n );\n}\n\n// ---------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------\n\nfunction requireParams(params: Record<string, string>, required: readonly string[]): void {\n const missing = required.filter((k) => !params[k] || params[k].trim() === '');\n if (missing.length > 0) {\n const err: Error & { statusCode?: number } = new Error(\n `missing required RequestParameter(s): ${missing.join(', ')}`\n );\n err.statusCode = 400;\n throw err;\n }\n}\n\nfunction splitCsv(value: string): string[] {\n return value\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n}\n\nfunction parseJsonOrEmpty(value: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(value);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return {};\n } catch {\n return {};\n }\n}\n\nfunction decodeBase64OrUtf8(value: string): Uint8Array {\n const trimmed = value.trim();\n // RFC 4648 base64 alphabet check (no whitespace, padding allowed).\n if (/^[A-Za-z0-9+/]+=*$/.test(trimmed) && trimmed.length % 4 === 0 && trimmed.length > 0) {\n try {\n return Buffer.from(trimmed, 'base64');\n } catch {\n /* fall through */\n }\n }\n return Buffer.from(value, 'utf8');\n}\n\nfunction okJson(response: unknown): ServiceIntegrationResult {\n // The SDK response includes a `$metadata` envelope we strip for\n // user-facing responses — matches AWS API Gateway's behavior, which\n // surfaces only the operation-specific fields.\n const stripped = stripSdkMetadata(response);\n return {\n statusCode: 200,\n body: JSON.stringify(stripped),\n headers: { 'content-type': 'application/json' },\n };\n}\n\nfunction stripSdkMetadata(obj: unknown): unknown {\n if (obj === null || obj === undefined || typeof obj !== 'object') return obj;\n if (Array.isArray(obj)) return obj;\n const { $metadata: _meta, ...rest } = obj as Record<string, unknown>;\n return rest;\n}\n\nfunction errorResponse(statusCode: number, message: string): ServiceIntegrationResult {\n return {\n statusCode,\n body: JSON.stringify({ message }),\n headers: { 'content-type': 'application/json' },\n };\n}\n\n/**\n * Translate an AWS SDK error to an HTTP response. AWS SDK v3 surfaces\n * errors as instances carrying `$metadata.httpStatusCode` + `name`;\n * we honor the status code when present, default to 500.\n */\nfunction translateSdkError(subtype: SupportedSubtype, err: unknown): ServiceIntegrationResult {\n if (err && typeof err === 'object') {\n const e = err as {\n name?: string;\n message?: string;\n $metadata?: { httpStatusCode?: number };\n statusCode?: number;\n };\n const status =\n typeof e.statusCode === 'number' && e.statusCode >= 100 && e.statusCode < 600\n ? e.statusCode\n : (e.$metadata?.httpStatusCode ?? 500);\n const body = {\n message: e.message ?? 'AWS SDK call failed',\n code: e.name ?? 'UnknownError',\n };\n logger.debug(`[${subtype}] SDK error (${status}): ${stringifyValue(body)}`);\n return {\n statusCode: status,\n body: JSON.stringify(body),\n headers: { 'content-type': 'application/json' },\n };\n }\n return errorResponse(500, `Unexpected error invoking ${subtype}: ${String(err)}`);\n}\n\n// ---------------------------------------------------------------------\n// ResponseParameters overlay\n// ---------------------------------------------------------------------\n\n/**\n * Apply HTTP API v2 `ResponseParameters` mapping (per AWS docs:\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html#http-api-mapping-supported-values).\n *\n * Keys are `<op>:header.<name>` or `overwrite:statuscode`. Values can\n * carry `$response.header.<name>`, `$context.<X>`, `$stageVariables.<X>`,\n * or be a static literal. JSONPath against `$response.body.X` is NOT\n * supported (would require SDK response parsing into the same shape\n * every subtype produces; deferred). Reserved headers (per AWS docs)\n * are rejected at this layer with a single-line debug log.\n *\n * Status-code lookup is by the SDK-returned `statusCode`. When the\n * exact code has no entry, the wildcard `'default'` entry is applied\n * if present (matches AWS deployed behavior).\n */\nexport function applyResponseParameters(\n base: ServiceIntegrationResult,\n responseParameters: Readonly<Record<string, Readonly<Record<string, string>>>> | undefined,\n responseCtx: ResponseParameterContext\n): ServiceIntegrationResult {\n if (!responseParameters) return base;\n const overlay =\n responseParameters[String(base.statusCode)] ?? responseParameters['default'] ?? undefined;\n if (!overlay) return base;\n\n let statusCode = base.statusCode;\n const headers: Record<string, string> = { ...base.headers };\n for (const [key, value] of Object.entries(overlay)) {\n if (typeof value !== 'string') continue;\n const resolved = resolveResponseValue(value, responseCtx, base);\n if (key === 'overwrite:statuscode') {\n const next = Number(resolved);\n if (Number.isInteger(next) && next >= 100 && next < 600) statusCode = next;\n continue;\n }\n const headerMatch = /^(append|overwrite|remove):header\\.(.+)$/i.exec(key);\n if (!headerMatch || !headerMatch[1] || !headerMatch[2]) continue;\n const op = headerMatch[1].toLowerCase();\n const name = headerMatch[2].toLowerCase();\n if (isReservedHeader(name)) {\n logger.debug(\n `ResponseParameters: header '${name}' is reserved by API Gateway and was skipped`\n );\n continue;\n }\n if (op === 'remove') {\n delete headers[name];\n } else if (op === 'overwrite') {\n headers[name] = resolved;\n } else if (op === 'append') {\n headers[name] = headers[name] ? `${headers[name]},${resolved}` : resolved;\n }\n }\n return { statusCode, body: base.body, headers };\n}\n\n/**\n * Context for `ResponseParameters` resolution.\n */\nexport interface ResponseParameterContext {\n context: Readonly<Record<string, string>>;\n stageVariables: Readonly<Record<string, string>>;\n}\n\nfunction resolveResponseValue(\n value: string,\n ctx: ResponseParameterContext,\n base: ServiceIntegrationResult\n): string {\n // Inline the same `${...}` interpolation engine as request-side. We\n // reuse the same dollar-bare form rules (whole-string match or\n // ${...} placeholders).\n if (value.startsWith('$') && !value.includes('${')) {\n const r = resolveSingleResponseRef(value, ctx, base);\n return r !== undefined ? r : value;\n }\n if (value.includes('${')) {\n let out = '';\n let i = 0;\n while (i < value.length) {\n const next = value.indexOf('${', i);\n if (next === -1) {\n out += value.slice(i);\n break;\n }\n out += value.slice(i, next);\n const end = value.indexOf('}', next + 2);\n if (end === -1) return value;\n const inner = value.slice(next + 2, end);\n const r = resolveSingleResponseRef('$' + inner, ctx, base);\n out += r ?? '';\n i = end + 1;\n }\n return out;\n }\n return value;\n}\n\nfunction resolveSingleResponseRef(\n ref: string,\n ctx: ResponseParameterContext,\n base: ServiceIntegrationResult\n): string | undefined {\n if (ref.startsWith('$response.header.')) {\n const name = ref.substring('$response.header.'.length).toLowerCase();\n return base.headers[name] ?? '';\n }\n if (ref.startsWith('$context.')) {\n return ctx.context[ref.substring('$context.'.length)] ?? '';\n }\n if (ref.startsWith('$stageVariables.')) {\n return ctx.stageVariables[ref.substring('$stageVariables.'.length)] ?? '';\n }\n return undefined;\n}\n\n/**\n * Subset of AWS's reserved-headers list relevant to response mapping.\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html#http-api-mapping-reserved-headers\n */\nconst RESERVED_HEADER_PREFIXES: readonly string[] = [\n 'access-control-',\n 'apigw-',\n 'x-amz-',\n 'x-amzn-',\n];\n\nconst RESERVED_HEADER_EXACT: readonly string[] = [\n 'authorization',\n 'connection',\n 'content-encoding',\n 'content-length',\n 'content-location',\n 'forwarded',\n 'keep-alive',\n 'origin',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailers',\n 'transfer-encoding',\n 'upgrade',\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-proto',\n 'via',\n];\n\nfunction isReservedHeader(lowerName: string): boolean {\n if (RESERVED_HEADER_EXACT.includes(lowerName)) return true;\n return RESERVED_HEADER_PREFIXES.some((p) => lowerName.startsWith(p));\n}\n","import { readCdkPath } from '../cli/cdk-path.js';\nimport type { StackInfo } from '../synthesis/assembly-reader.js';\nimport type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { RouteDiscoveryError } from '../utils/error-handler.js';\nimport { stringifyValue } from '../utils/stringify.js';\nimport { resolveLambdaArnIntrinsic as resolveLambdaArnShared } from './intrinsic-lambda-arn.js';\nimport { pickRefLogicalId } from './intrinsic-utils.js';\nimport { isSupportedSubtype, type SupportedSubtype } from './httpv2-service-integration.js';\nimport type {\n AwsLambdaIntegrationConfig,\n HttpIntegrationConfig,\n HttpProxyIntegrationConfig,\n MockIntegrationConfig,\n} from './rest-v1-integrations.js';\nimport type { IntegrationResponseEntry } from './integration-response-selector.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Union of REST v1 non-AWS_PROXY integration configurations captured at\n * discovery time. The http-server dispatches on `kind` to invoke the\n * matching handler in `src/local/rest-v1-integrations.ts`.\n *\n * Only populated for REST v1 routes that classify as one of the four\n * supported non-AWS_PROXY kinds (closes #457). AWS_PROXY routes,\n * synthetic CORS preflight routes, and unsupported routes leave this\n * field undefined.\n */\nexport type RestV1IntegrationConfig =\n | MockIntegrationConfig\n | HttpProxyIntegrationConfig\n | HttpIntegrationConfig\n | AwsLambdaIntegrationConfig;\n\n/**\n * One discovered API → Lambda route for `cdkl start-api`.\n *\n * Walks the synthesized template, extracts every API Gateway REST v1\n * route, ApiGatewayV2 (HTTP) route, and Function URL, and produces a flat\n * list of routes the HTTP server can match on.\n *\n * `apiVersion` governs the event-shape construction: REST v1 (`AWS::ApiGateway::*`)\n * uses the v1 proxy event shape (`multiValueHeaders` etc.); HTTP API and\n * Function URL use the v2 shape (`requestContext.http`, `cookies` array).\n *\n * Per-route classification (see {@link DiscoveredRoute.unsupported} /\n * {@link DiscoveredRoute.mockCors}):\n * - **Supported** — `unsupported` and `mockCors` both unset. The server\n * dispatches to the route's Lambda via the container pool.\n * - **Synthetic CORS preflight** — `mockCors` set. The server answers\n * OPTIONS requests directly with the captured headers (no Lambda\n * invocation). Used to emulate CDK's `defaultCorsPreflightOptions`,\n * which synthesizes a REST v1 OPTIONS Method backed by a MOCK\n * integration with literal `method.response.header.*` parameters.\n * - **Unsupported, deferred error** — `unsupported` set. The server\n * surfaces the route in the route table and returns HTTP 501 with\n * the `reason` in the JSON body if and when the route is hit. Boot\n * proceeds normally so the rest of the API surface stays reachable.\n *\n * The discovery layer still **hard-errors** via {@link RouteDiscoveryError}\n * on template-structural problems it cannot generate a meaningful route\n * from (missing `Integration` on an `AWS::ApiGateway::Method`,\n * non-Ref `RestApiId` / `ApiId`, malformed Route `Target`, ParentId cycle\n * / missing parent / wrong parent type / missing PathPart). These would\n * leave the server in a state where the unsupported-route 501 path\n * doesn't have enough info to identify what was misconfigured.\n */\nexport interface DiscoveredRoute {\n /** HTTP method or `'ANY'`. REST v1 spec routes `'ANY'` to every method. */\n method: string;\n /** Path pattern with `{param}` placeholders, `{proxy+}` for greedy, or `'$default'`. */\n pathPattern: string;\n /** Logical ID of the Lambda the route invokes. */\n lambdaLogicalId: string;\n /** Where the route originated. Drives event-shape selection downstream. */\n source: 'http-api' | 'rest-v1' | 'function-url';\n /** Event-shape version: 'v1' for REST v1, 'v2' for HTTP API and Function URL. */\n apiVersion: 'v1' | 'v2';\n /**\n * REST v1: the resolved Stage name (or `'$default'` if none was attached).\n * HTTP API: `'$default'`. Function URL: `'$default'`.\n *\n * For HTTP API + REST v1, this is the **default** stage name picked\n * at discovery time. The CLI's `--stage <name>` override is applied\n * by `attachStageContext` (PR 8c) which mutates `stage` on each\n * route — see `src/local/stage-resolver.ts` for the rules.\n */\n stage: string;\n /**\n * Logical ID of the resource that owns this route's surface-level\n * config (CORS, stage variables, etc.):\n * - REST v1 routes: the `AWS::ApiGateway::RestApi`.\n * - HTTP API routes: the `AWS::ApiGatewayV2::Api`.\n * - Function URL routes: the `AWS::Lambda::Url` itself (issue #644\n * — the Function URL is its own surface-config-bearing resource;\n * it carries the `Cors` block that `buildCorsConfigByApiId`\n * surfaces).\n *\n * Used by the CORS handler + stage-resolver to look up per-surface\n * config. Despite the name `apiLogicalId`, the value is \"the\n * surface-config-bearing resource\" rather than literally \"an API\n * resource\" — that distinction is load-bearing for Function URLs.\n */\n apiLogicalId?: string;\n /**\n * Name of the stack the parent API resource (or backing Lambda for\n * Function URLs) lives in. Populated for every route so the\n * `--api` filter can accept the **stack-qualified logical id**\n * form (`MyStack:MyHttpApi`) — mirrors `cdkl invoke` /\n * `cdkl run-task` target syntax.\n */\n apiStackName?: string;\n /**\n * CDK Construct path (`aws:cdk:path` metadata) of the parent API\n * resource (or the backing Lambda for Function URLs). Populated\n * for every route when the synthesized resource carries the\n * metadata. Used by `--api` to accept the **CDK display path**\n * form (`MyStack/MyHttpApi`) with prefix-rule matching (matches\n * the input exactly OR when the input is the parent L2 path that\n * resolves down to this resource's L1 child) — mirrors the same\n * upstream CDK construct-path prefix rule.\n */\n apiCdkPath?: string;\n /**\n * Stage variables for the route's selected Stage (PR 8c). `null` when\n * the route's Stage has no Variables, or for routes without a Stage\n * (Function URLs). Populated by `attachStageContext` after discovery\n * — `discoverRoutes` itself does NOT set this field.\n */\n stageVariables?: Record<string, string> | null;\n /**\n * Function URL invoke mode. Defaults to `'BUFFERED'` (the standard\n * request/response shape); `'RESPONSE_STREAM'` opts into Lambda\n * response streaming via the RIE streaming protocol — the local\n * server invokes the Lambda with the\n * `Lambda-Runtime-Function-Response-Mode: streaming` header and\n * pipes the response chunks to the HTTP client with\n * `Transfer-Encoding: chunked`. Only set on `source: 'function-url'`\n * routes; REST v1 / HTTP API v2 routes are always buffered.\n */\n invokeMode?: 'BUFFERED' | 'RESPONSE_STREAM';\n /**\n * Set on routes that cdk-local discovered but cannot dispatch to a Lambda.\n * The HTTP server returns HTTP 501 + `{\"message\": \"Not Implemented\",\n * \"reason\": <reason>}` when these routes are hit. Examples:\n * non-AWS_PROXY REST v1 integrations (`MOCK` not matching the CORS\n * preflight shape, `AWS`, `HTTP`, `HTTP_PROXY`), HTTP API v2\n * service integrations (`IntegrationSubtype` set), WebSocket APIs,\n * Function URLs with an unrecognized `AuthType` (anything other than\n * `'NONE'` / `'AWS_IAM'`) or with an `InvokeMode` value outside\n * `BUFFERED` / `RESPONSE_STREAM`, and routes whose Lambda Arn\n * intrinsic cannot be resolved against the same template. (Function\n * URLs with `InvokeMode: RESPONSE_STREAM` are normal routes dispatched\n * via the streaming protocol — #467. Function URLs with\n * `AuthType: AWS_IAM` are normal routes verified through the same\n * SigV4 pipeline as REST v1 AWS_IAM — #621.)\n *\n * Mutually exclusive with {@link DiscoveredRoute.mockCors}. When set,\n * `lambdaLogicalId` may be the empty string (we never need to dispatch\n * to it); the field is preserved when it COULD be resolved (e.g. an\n * `AuthType` cdk-local doesn't recognize still knows its Lambda).\n */\n unsupported?: { reason: string };\n /**\n * Set on synthetic CORS preflight routes derived from a REST v1\n * `AWS::ApiGateway::Method` whose `HttpMethod === 'OPTIONS'`,\n * `Integration.Type === 'MOCK'`, and `IntegrationResponses[]` carries\n * literal `method.response.header.*` mapping parameters (the shape\n * CDK's `defaultCorsPreflightOptions` synthesizes). The HTTP server\n * intercepts matching OPTIONS requests and returns the captured\n * status + headers directly without invoking any Lambda.\n *\n * Mutually exclusive with {@link DiscoveredRoute.unsupported}.\n * `lambdaLogicalId` is the empty string on these routes (there is no\n * Lambda to dispatch to). Non-OPTIONS MOCK methods, and OPTIONS MOCK\n * methods without literal `method.response.header.*` parameters, become\n * `unsupported` instead — cdk-local cannot run their VTL mapping templates.\n */\n mockCors?: { statusCode: number; headers: Record<string, string> };\n /**\n * Set on HTTP API v2 routes wired to an AWS service integration\n * (`IntegrationType: AWS_PROXY` + `IntegrationSubtype`) — see AWS\n * docs: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html\n *\n * cdk-local dispatches the route via `httpv2-service-integration.ts`\n * (per-subtype SDK adapter); `lambdaLogicalId` is the empty string\n * (no Lambda backs the route). The `subtype` value is one of the\n * AWS-documented supported subtypes; unrecognized `IntegrationSubtype`\n * values fall through to {@link DiscoveredRoute.unsupported} so they\n * still surface a clear 501.\n *\n * Mutually exclusive with {@link DiscoveredRoute.unsupported} and\n * {@link DiscoveredRoute.mockCors}.\n */\n serviceIntegration?: {\n subtype: SupportedSubtype;\n requestParameters: Readonly<Record<string, unknown>>;\n responseParameters?: Readonly<Record<string, Readonly<Record<string, string>>>>;\n };\n /**\n * REST v1 non-AWS_PROXY integration configuration. Populated for the\n * four supported non-AWS_PROXY kinds (`AWS` Lambda non-proxy, `HTTP`,\n * `HTTP_PROXY`, `MOCK` non-CORS); when set, the http-server dispatches\n * via `src/local/rest-v1-integrations.ts` instead of the AWS_PROXY\n * container-pool path.\n *\n * Mutually exclusive with {@link DiscoveredRoute.unsupported} /\n * {@link DiscoveredRoute.mockCors}. AWS_PROXY routes leave this\n * undefined and use `lambdaLogicalId` for dispatch. Closes #457.\n */\n restV1Integration?: RestV1IntegrationConfig;\n /** Diagnostic only — used in route-table output and error messages. */\n declaredAt: string;\n}\n\n/**\n * Walk every stack's template and produce a flat list of discovered\n * routes. Routes are de-duplicated only when their (method, pathPattern,\n * lambdaLogicalId, stage) tuple is identical — different stacks may\n * legitimately host different APIs that mount the same path.\n *\n * Each route is one of three classes (see {@link DiscoveredRoute}):\n * - normal (no flag set);\n * - synthetic CORS preflight (`mockCors` set);\n * - deferred-error unsupported (`unsupported` set).\n *\n * Throws {@link RouteDiscoveryError} only on template-structural failures\n * the discovery layer cannot generate a meaningful route from (e.g.\n * missing Integration property, ParentId cycle, non-Ref RestApiId). Per-\n * route integration unsupportedness now flows through `unsupported` and\n * is surfaced as HTTP 501 at request time.\n */\nexport function discoverRoutes(stacks: readonly StackInfo[]): DiscoveredRoute[] {\n const routes: DiscoveredRoute[] = [];\n const errors: string[] = [];\n\n for (const stack of stacks) {\n const template = stack.template;\n const resources = template.Resources ?? {};\n\n for (const [logicalId, resource] of Object.entries(resources)) {\n try {\n switch (resource.Type) {\n case 'AWS::ApiGateway::Method':\n routes.push(...discoverRestV1Method(logicalId, resource, template, stack.stackName));\n break;\n case 'AWS::ApiGatewayV2::Route':\n routes.push(...discoverHttpApiRoute(logicalId, resource, template, stack.stackName));\n break;\n case 'AWS::Lambda::Url':\n routes.push(...discoverFunctionUrl(logicalId, resource, template, stack.stackName));\n break;\n default:\n // Filter the known parent types early so we don't log noise.\n break;\n }\n } catch (err) {\n errors.push(err instanceof Error ? err.message : String(err));\n }\n }\n }\n\n if (errors.length > 0) {\n throw new RouteDiscoveryError(\n `${getEmbedConfig().cliName} start-api: ${errors.length} malformed route(s) in the synthesized template:\\n` +\n errors.map((e) => ` - ${e}`).join('\\n')\n );\n }\n\n return routes;\n}\n\n/**\n * Discover REST v1 routes from an `AWS::ApiGateway::Method` resource.\n *\n * Walks the `Resource.ParentId` chain up to the parent `RestApi` to build\n * the full path, then looks up the corresponding Stage (when one is\n * attached to the same RestApi) so `requestContext.stage` is realistic.\n *\n * Per-integration classification (see {@link DiscoveredRoute}):\n * - `Integration.Type === 'AWS_PROXY'` → normal route.\n * - `HttpMethod === 'OPTIONS'` + `Type === 'MOCK'` + `IntegrationResponses`\n * contain literal `method.response.header.*` mapping params → synthetic\n * CORS preflight (`mockCors` set). Emulates CDK's\n * `defaultCorsPreflightOptions` output.\n * - All other `Integration.Type` values (`MOCK` without CORS shape,\n * `AWS`, `HTTP`, `HTTP_PROXY`) → unsupported route. The HTTP server\n * returns 501 when the route is hit; boot proceeds.\n *\n * Hard-errors on template-structural problems (missing Integration,\n * non-Ref RestApiId, ParentId-chain failures).\n *\n * Method.HttpMethod values of `'ANY'` are returned as a single route with\n * `method='ANY'`; the matcher routes any HTTP method to the Lambda.\n */\nfunction discoverRestV1Method(\n logicalId: string,\n resource: TemplateResource,\n template: CloudFormationTemplate,\n stackName: string\n): DiscoveredRoute[] {\n const props = resource.Properties ?? {};\n const integration = props['Integration'] as Record<string, unknown> | undefined;\n if (!integration) {\n throw new Error(\n `${stackName}/${logicalId} (AWS::ApiGateway::Method): missing Integration property`\n );\n }\n\n const restApiId = props['RestApiId'];\n const restApiLogicalId = pickRefLogicalId(restApiId);\n if (!restApiLogicalId) {\n throw new Error(\n `${stackName}/${logicalId} (AWS::ApiGateway::Method): RestApiId must be a { Ref: '...' } reference (got ${shortJson(\n restApiId\n )}).`\n );\n }\n\n const resourceId = props['ResourceId'];\n const path = buildRestV1Path(resourceId, restApiLogicalId, template, stackName, logicalId);\n\n const httpMethod = stringifyValue(props['HttpMethod'] ?? 'ANY');\n const stage = pickRestV1Stage(restApiLogicalId, template);\n const restApiCdkPath = readApiCdkPath(restApiLogicalId, template);\n const baseRoute: Omit<DiscoveredRoute, 'method' | 'pathPattern' | 'lambdaLogicalId'> = {\n source: 'rest-v1',\n apiVersion: 'v1',\n stage,\n apiLogicalId: restApiLogicalId,\n apiStackName: stackName,\n ...(restApiCdkPath !== undefined && { apiCdkPath: restApiCdkPath }),\n declaredAt: `${stackName}/${logicalId}`,\n };\n\n const integrationType = integration['Type'];\n\n // Non-AWS_PROXY integration types — emulate via the dispatcher in\n // `src/local/rest-v1-integrations.ts` (closes #457).\n // The integration types AWS accepts on REST v1:\n // - AWS_PROXY (handled below)\n // - AWS (Lambda non-proxy with VTL)\n // - HTTP / HTTP_PROXY\n // - MOCK (non-CORS) — CORS preflight is handled by a synthetic shape\n // extracted FIRST so the http-server can return literal header\n // values without running a VTL response template.\n if (integrationType === 'MOCK') {\n // OPTIONS + MOCK + literal `method.response.header.*` ResponseParameters\n // is the CDK `defaultCorsPreflightOptions` synth shape — surface as a\n // synthetic CORS-preflight route the http-server answers directly\n // without running VTL.\n if (httpMethod === 'OPTIONS') {\n const preflight = extractRestV1MockCorsConfig(integration);\n if (preflight) {\n return [\n {\n ...baseRoute,\n method: 'OPTIONS',\n pathPattern: path,\n lambdaLogicalId: '',\n mockCors: preflight,\n },\n ];\n }\n }\n // Any other MOCK integration (non-OPTIONS, or OPTIONS without the\n // canonical CORS-preflight shape) flows through the full MOCK\n // dispatcher in `rest-v1-integrations.ts`.\n const config = buildMockIntegrationConfig(integration);\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n restV1Integration: config,\n },\n ];\n }\n if (integrationType === 'HTTP_PROXY') {\n const config = buildHttpProxyIntegrationConfig(integration, stackName, logicalId);\n if (config.kind === 'unsupported') {\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n unsupported: { reason: config.reason },\n },\n ];\n }\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n restV1Integration: config.config,\n },\n ];\n }\n if (integrationType === 'HTTP') {\n const config = buildHttpIntegrationConfig(integration, stackName, logicalId);\n if (config.kind === 'unsupported') {\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n unsupported: { reason: config.reason },\n },\n ];\n }\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n restV1Integration: config.config,\n },\n ];\n }\n if (integrationType === 'AWS') {\n // AWS integration: Lambda non-proxy (the common case CDK emits) OR\n // direct AWS service integration (S3 / SQS / SNS / DynamoDB). cdk-local v1 emulates the Lambda non-proxy shape; other services surface as\n // deferred 501. We detect Lambda by inspecting the Uri shape — any\n // `:lambda:path/2015-03-31/functions/` marker identifies Lambda.\n const config = buildAwsIntegrationConfig(integration, stackName, logicalId);\n if (config.kind === 'unsupported') {\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n unsupported: { reason: config.reason },\n },\n ];\n }\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: config.config.lambdaLogicalId,\n restV1Integration: config.config,\n },\n ];\n }\n if (integrationType !== 'AWS_PROXY') {\n // Unknown integration type — defensive 501.\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n unsupported: {\n reason: `${stackName}/${logicalId}: unknown REST v1 integration type '${String(integrationType)}' (expected AWS_PROXY / AWS / HTTP / HTTP_PROXY / MOCK).`,\n },\n },\n ];\n }\n\n // AWS_PROXY: resolve the Lambda Arn. Unresolvable shapes become\n // unsupported routes (the route is identifiable, we just can't reach\n // its handler — e.g. cross-stack reference, imported Lambda).\n const integrationUri = integration['Uri'];\n const arnOutcome = resolveLambdaArnOutcome(integrationUri);\n if (arnOutcome.kind === 'unsupported') {\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: '',\n unsupported: {\n reason: `${stackName}/${logicalId}.Integration.Uri: ${arnOutcome.detail} (got ${shortJson(\n integrationUri\n )}). Lambda Arn intrinsics on cross-stack / imported references are not resolvable locally; deploy the producer stack and use \\`${getEmbedConfig().cliName} invoke --from-state\\` shapes if you need it.`,\n },\n },\n ];\n }\n\n return [\n {\n ...baseRoute,\n method: httpMethod,\n pathPattern: path,\n lambdaLogicalId: arnOutcome.logicalId,\n },\n ];\n}\n\n/**\n * Extract the canonical CORS-preflight headers from a REST v1 MOCK\n * Method's `Integration.IntegrationResponses[0]`. Returns `undefined`\n * when the shape isn't a CORS preflight (no IntegrationResponses, no\n * `method.response.header.*` mapping parameters, or any individual\n * mapping parameter we could not evaluate locally — see below).\n *\n * AWS represents header literals in `ResponseParameters` with surrounding\n * single-quotes (e.g. `\"'*'\"` for `*`). The single-quote wrappers are\n * stripped to produce the canonical header value the local server emits.\n *\n * **All-or-nothing**: if any `method.response.header.*` entry is\n * intrinsic-valued (`Fn::Sub`, `Ref` etc.), unquoted, or otherwise\n * not a string-literal-with-quotes, the WHOLE preflight falls through\n * to the unsupported class. Emitting a partial preflight with some\n * headers missing would silently break CORS in the browser (the\n * preflight succeeds, then the actual request hits a CORS error the\n * user has to debug through Network panel) — caller's the better\n * place to surface the underlying VTL-requirement via the 501 path.\n *\n * Only the first `IntegrationResponses` entry is consulted. CDK's\n * `defaultCorsPreflightOptions` emits exactly one entry; hand-rolled\n * multi-status MOCK preflights are an unsupported v1 limitation.\n */\nfunction extractRestV1MockCorsConfig(\n integration: Record<string, unknown>\n): { statusCode: number; headers: Record<string, string> } | undefined {\n const responses = integration['IntegrationResponses'];\n if (!Array.isArray(responses) || responses.length === 0) return undefined;\n const first = responses[0];\n if (!first || typeof first !== 'object') return undefined;\n const entry = first as Record<string, unknown>;\n const responseParameters = entry['ResponseParameters'];\n if (\n !responseParameters ||\n typeof responseParameters !== 'object' ||\n Array.isArray(responseParameters)\n ) {\n return undefined;\n }\n\n const headers: Record<string, string> = {};\n let sawAnyHeader = false;\n for (const [key, raw] of Object.entries(responseParameters as Record<string, unknown>)) {\n const m = /^method\\.response\\.header\\.(.+)$/.exec(key);\n if (!m) continue;\n sawAnyHeader = true;\n const headerName = m[1]!;\n // AWS literal-value convention: surround the literal with single\n // quotes. Anything else (an intrinsic, an unquoted reference) we\n // can't evaluate locally. All-or-nothing: reject the whole preflight\n // so the route falls through to the 501 path with the full reason,\n // rather than silently emitting a partial CORS response the browser\n // accepts AT the preflight but then chokes on at the actual request.\n if (typeof raw !== 'string') return undefined;\n if (raw.length < 2 || raw[0] !== \"'\" || raw[raw.length - 1] !== \"'\") return undefined;\n headers[headerName] = raw.slice(1, -1);\n }\n if (!sawAnyHeader) return undefined;\n\n // AWS represents the status code as a string. Default to 204 (the CDK\n // default for `defaultCorsPreflightOptions`) when it's missing or\n // unparseable.\n const statusCodeRaw = entry['StatusCode'];\n const parsed = typeof statusCodeRaw === 'string' ? Number.parseInt(statusCodeRaw, 10) : NaN;\n const statusCode = Number.isFinite(parsed) ? parsed : 204;\n\n return { statusCode, headers };\n}\n\n/**\n * Marker sequence on a Lambda invoke ARN — used to tell apart REST v1\n * `AWS` integrations whose backend is Lambda (`functions/<arn>/invocations`)\n * from other AWS-service integrations (`:s3:path/...`, `:sqs:path/...`).\n * Closes #457's AWS-vs-Lambda discrimination.\n */\nconst LAMBDA_INVOKE_PATH = ':lambda:path/2015-03-31/functions/';\n\n/** Parsed AWS integration outcome — either a populated config or an unsupported reason. */\ntype ConfigOrUnsupported<T> =\n | { kind: 'config'; config: T }\n | { kind: 'unsupported'; reason: string };\n\n/**\n * Build a MOCK integration config for `cdkl start-api` dispatch.\n *\n * Pulls `Integration.RequestTemplates['application/json']` (drives MOCK\n * status-code selection — AWS reads `{\"statusCode\": N}` from the rendered\n * template) and `Integration.IntegrationResponses[]` (drives the shaped\n * response).\n */\nfunction buildMockIntegrationConfig(integration: Record<string, unknown>): MockIntegrationConfig {\n const requestTemplate = pickStringFromRecord(integration['RequestTemplates'], 'application/json');\n const responses = readIntegrationResponses(integration);\n return {\n kind: 'mock',\n requestTemplate: requestTemplate ?? undefined,\n responses,\n };\n}\n\n/**\n * Build a HTTP_PROXY integration config. The Uri must be a literal\n * string at template-author time — `Fn::Sub` shapes with literal\n * placeholders are rare and unsupported in v1 (surfaces as a 501 with\n * a clear reason).\n */\nfunction buildHttpProxyIntegrationConfig(\n integration: Record<string, unknown>,\n stackName: string,\n logicalId: string\n): ConfigOrUnsupported<HttpProxyIntegrationConfig> {\n const uri = integration['Uri'];\n if (typeof uri !== 'string' || uri.length === 0) {\n return {\n kind: 'unsupported',\n reason: `${stackName}/${logicalId}: HTTP_PROXY Integration.Uri must be a literal string in v1 (${getEmbedConfig().cliName} start-api does not resolve Fn::Sub / Fn::Join in HTTP_PROXY Uris); got ${shortJson(uri)}.`,\n };\n }\n const integrationHttpMethod = pickStringField(integration, 'IntegrationHttpMethod');\n const requestParameters = pickStringRecord(integration['RequestParameters']);\n const responses = readIntegrationResponses(integration);\n return {\n kind: 'config',\n config: {\n kind: 'http-proxy',\n uri,\n ...(integrationHttpMethod !== undefined && { integrationHttpMethod }),\n ...(requestParameters !== undefined && { requestParameters }),\n responses,\n },\n };\n}\n\n/**\n * Build an HTTP (non-proxy) integration config. Like HTTP_PROXY but with\n * `RequestTemplates` for VTL transformation.\n */\nfunction buildHttpIntegrationConfig(\n integration: Record<string, unknown>,\n stackName: string,\n logicalId: string\n): ConfigOrUnsupported<HttpIntegrationConfig> {\n const uri = integration['Uri'];\n if (typeof uri !== 'string' || uri.length === 0) {\n return {\n kind: 'unsupported',\n reason: `${stackName}/${logicalId}: HTTP Integration.Uri must be a literal string in v1 (${getEmbedConfig().cliName} start-api does not resolve Fn::Sub / Fn::Join in HTTP Uris); got ${shortJson(uri)}.`,\n };\n }\n const integrationHttpMethod = pickStringField(integration, 'IntegrationHttpMethod');\n const requestParameters = pickStringRecord(integration['RequestParameters']);\n const requestTemplates = pickStringRecord(integration['RequestTemplates']);\n const responses = readIntegrationResponses(integration);\n return {\n kind: 'config',\n config: {\n kind: 'http',\n uri,\n ...(integrationHttpMethod !== undefined && { integrationHttpMethod }),\n ...(requestParameters !== undefined && { requestParameters }),\n ...(requestTemplates !== undefined && { requestTemplates }),\n responses,\n },\n };\n}\n\n/**\n * Build an AWS integration config. Branches on whether the integration\n * targets a Lambda (`:lambda:path/2015-03-31/functions/<arn>/invocations`)\n * or a non-Lambda AWS service (`:s3:path/...` / `:sqs:action/...` etc.).\n *\n * cdkl v1 supports Lambda non-proxy AWS integrations end-to-end. Non-\n * Lambda AWS service integrations surface as deferred-501 unsupported\n * routes — they would require an AWS SDK client per service, IAM\n * credential threading, and a sizable per-service unit-test matrix.\n * See [docs/local-emulation.md](docs/local-emulation.md) for the deferred\n * AWS-service-action list.\n */\nfunction buildAwsIntegrationConfig(\n integration: Record<string, unknown>,\n stackName: string,\n logicalId: string\n): ConfigOrUnsupported<AwsLambdaIntegrationConfig> {\n const uri = integration['Uri'];\n const isLambda = uriContainsLambdaMarker(uri);\n if (!isLambda) {\n return {\n kind: 'unsupported',\n reason: `${stackName}/${logicalId}: REST v1 AWS integration targeting a non-Lambda service (Uri ${shortJson(uri)}) is not emulated locally in ${getEmbedConfig().binaryName} v1. Lambda non-proxy AWS integrations are supported; direct AWS service integrations (S3 / SQS / SNS / DynamoDB) require deploying to AWS. See https://github.com/go-to-k/cdk-local/blob/main/docs/local-emulation.md.`,\n };\n }\n const arnOutcome = resolveLambdaArnOutcome(uri);\n if (arnOutcome.kind === 'unsupported') {\n return {\n kind: 'unsupported',\n reason: `${stackName}/${logicalId}.Integration.Uri: ${arnOutcome.detail} (got ${shortJson(uri)}). Lambda Arn intrinsics on cross-stack / imported references are not resolvable locally.`,\n };\n }\n const requestTemplates = pickStringRecord(integration['RequestTemplates']);\n const responses = readIntegrationResponses(integration);\n return {\n kind: 'config',\n config: {\n kind: 'aws-lambda',\n lambdaLogicalId: arnOutcome.logicalId,\n ...(requestTemplates !== undefined && { requestTemplates }),\n responses,\n },\n };\n}\n\n/**\n * Determine whether an `Integration.Uri` references a Lambda invoke path.\n * Recognises the canonical Lambda invoke ARN shape across the same\n * intrinsic forms `intrinsic-lambda-arn.ts` accepts (the shared resolver\n * never produces this Boolean directly, so we walk the shape here).\n */\nfunction uriContainsLambdaMarker(uri: unknown): boolean {\n if (typeof uri === 'string') return uri.includes(LAMBDA_INVOKE_PATH);\n if (uri && typeof uri === 'object' && !Array.isArray(uri)) {\n const obj = uri as Record<string, unknown>;\n if ('Fn::Sub' in obj) {\n const v = obj['Fn::Sub'];\n if (typeof v === 'string') return v.includes(LAMBDA_INVOKE_PATH);\n if (Array.isArray(v) && typeof v[0] === 'string') return v[0].includes(LAMBDA_INVOKE_PATH);\n }\n if ('Fn::Join' in obj) {\n const join = obj['Fn::Join'];\n if (Array.isArray(join) && join.length === 2 && Array.isArray(join[1])) {\n for (const piece of join[1]) {\n if (typeof piece === 'string' && piece.includes(LAMBDA_INVOKE_PATH)) return true;\n }\n }\n }\n // Plain `Ref` / `Fn::GetAtt: [..., 'Arn']` shapes always refer to\n // Lambda in REST v1 (the only intrinsic-targetable resource on AWS\n // integration is Lambda; AWS service Uris use literal-string Fn::Sub\n // forms in CDK templates).\n if ('Ref' in obj || 'Fn::GetAtt' in obj) return true;\n }\n return false;\n}\n\n/**\n * Read `Integration.IntegrationResponses[]` from a Method's Integration\n * sub-object and return the entries cdk-local's dispatchers consume.\n *\n * Defensive: rejects non-object entries with a clear inline warning.\n */\nfunction readIntegrationResponses(\n integration: Record<string, unknown>\n): IntegrationResponseEntry[] {\n const raw = integration['IntegrationResponses'];\n if (!Array.isArray(raw)) return [];\n const out: IntegrationResponseEntry[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue;\n const obj = entry as Record<string, unknown>;\n const statusCode = obj['StatusCode'];\n if (statusCode === undefined) continue;\n // Defensive: only accept string / number forms. Intrinsic-valued\n // StatusCodes are theoretically possible but never emitted by CDK\n // (and we couldn't evaluate them locally anyway).\n if (typeof statusCode !== 'string' && typeof statusCode !== 'number') continue;\n const e: IntegrationResponseEntry = { StatusCode: String(statusCode) };\n if (typeof obj['SelectionPattern'] === 'string') e.SelectionPattern = obj['SelectionPattern'];\n const responseParameters = pickStringRecord(obj['ResponseParameters']);\n if (responseParameters !== undefined) e.ResponseParameters = responseParameters;\n const responseTemplates = pickStringRecord(obj['ResponseTemplates']);\n if (responseTemplates !== undefined) e.ResponseTemplates = responseTemplates;\n if (typeof obj['ContentHandling'] === 'string') e.ContentHandling = obj['ContentHandling'];\n out.push(e);\n }\n return out;\n}\n\nfunction pickStringField(props: Record<string, unknown>, key: string): string | undefined {\n const v = props[key];\n return typeof v === 'string' ? v : undefined;\n}\n\nfunction pickStringRecord(value: unknown): Record<string, string> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;\n const out: Record<string, string> = {};\n let any = false;\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (typeof v === 'string') {\n out[k] = v;\n any = true;\n }\n }\n return any ? out : undefined;\n}\n\nfunction pickStringFromRecord(value: unknown, key: string): string | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;\n const v = (value as Record<string, unknown>)[key];\n return typeof v === 'string' ? v : undefined;\n}\n\n/**\n * Walk a chain of `AWS::ApiGateway::Resource` parent pointers up to the\n * `RestApi` root to build the full path. Each `Resource` contributes a\n * `PathPart` segment; the `RestApi` itself contributes the leading `/`.\n *\n * The walk hard-fails on cycles, missing parents, and non-Ref ParentId\n * intrinsics — all of which would silently corrupt the path otherwise.\n */\nfunction buildRestV1Path(\n resourceIdIntrinsic: unknown,\n restApiLogicalId: string,\n template: CloudFormationTemplate,\n stackName: string,\n methodLogicalId: string\n): string {\n // Special case: `ResourceId: { 'Fn::GetAtt': [restApi, 'RootResourceId'] }`\n // means the method is mounted at `/`. CDK's RestApi.root.addMethod() emits\n // exactly this shape.\n if (\n resourceIdIntrinsic &&\n typeof resourceIdIntrinsic === 'object' &&\n !Array.isArray(resourceIdIntrinsic)\n ) {\n const obj = resourceIdIntrinsic as Record<string, unknown>;\n if ('Fn::GetAtt' in obj) {\n const arg = obj['Fn::GetAtt'];\n if (Array.isArray(arg) && arg.length === 2 && arg[1] === 'RootResourceId') {\n return '/';\n }\n }\n }\n\n const resourceLogicalId = pickRefLogicalId(resourceIdIntrinsic);\n if (!resourceLogicalId) {\n throw new Error(\n `${stackName}/${methodLogicalId}: ResourceId must be { Ref: '...' } or { 'Fn::GetAtt': [..., 'RootResourceId'] } (got ${shortJson(\n resourceIdIntrinsic\n )}).`\n );\n }\n\n const segments: string[] = [];\n const visited = new Set<string>();\n let cursor: string | undefined = resourceLogicalId;\n\n while (cursor && cursor !== restApiLogicalId) {\n if (visited.has(cursor)) {\n throw new Error(\n `${stackName}/${methodLogicalId}: cycle detected in AWS::ApiGateway::Resource ParentId chain at ${cursor}`\n );\n }\n visited.add(cursor);\n const node: TemplateResource | undefined = template.Resources?.[cursor];\n if (!node) {\n throw new Error(\n `${stackName}/${methodLogicalId}: ParentId chain references missing resource '${cursor}'`\n );\n }\n if (node.Type !== 'AWS::ApiGateway::Resource') {\n throw new Error(\n `${stackName}/${methodLogicalId}: ParentId chain hit ${node.Type} (expected AWS::ApiGateway::Resource or RestApi root)`\n );\n }\n const nodeProps: Record<string, unknown> = node.Properties ?? {};\n const pathPart = nodeProps['PathPart'];\n if (typeof pathPart !== 'string') {\n throw new Error(\n `${stackName}/${methodLogicalId}: AWS::ApiGateway::Resource '${cursor}' missing PathPart`\n );\n }\n segments.unshift(pathPart);\n\n const parentId: unknown = nodeProps['ParentId'];\n // Fn::GetAtt RootResourceId means we've reached the RestApi root.\n if (\n parentId &&\n typeof parentId === 'object' &&\n !Array.isArray(parentId) &&\n 'Fn::GetAtt' in (parentId as Record<string, unknown>)\n ) {\n const arg = (parentId as Record<string, unknown>)['Fn::GetAtt'];\n if (Array.isArray(arg) && arg[1] === 'RootResourceId') break;\n }\n cursor = pickRefLogicalId(parentId) ?? undefined;\n }\n\n return '/' + segments.join('/');\n}\n\n/**\n * Find the first `AWS::ApiGateway::Stage` attached to the given RestApi\n * and return its `StageName`. Falls back to `'$default'` when no Stage\n * resource is attached (e.g. CDK's `RestApi` always emits a default stage,\n * but a hand-rolled template may omit it).\n */\nfunction pickRestV1Stage(restApiLogicalId: string, template: CloudFormationTemplate): string {\n const resources = template.Resources ?? {};\n for (const [, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ApiGateway::Stage') continue;\n const props = resource.Properties ?? {};\n const ref = pickRefLogicalId(props['RestApiId']);\n if (ref === restApiLogicalId) {\n const stageName = props['StageName'];\n if (typeof stageName === 'string') return stageName;\n }\n }\n return '$default';\n}\n\n/**\n * Discover routes from an `AWS::ApiGatewayV2::Route` resource.\n *\n * Filters out:\n * - WebSocket APIs (`AWS::ApiGatewayV2::Api.ProtocolType === 'WEBSOCKET'`).\n * - Service integrations (`Integration.IntegrationSubtype` set), even\n * when their type is `AWS_PROXY` — those are SQS / EventBridge etc.\n * direct integrations (no Lambda involved).\n */\nfunction discoverHttpApiRoute(\n logicalId: string,\n resource: TemplateResource,\n template: CloudFormationTemplate,\n stackName: string\n): DiscoveredRoute[] {\n const props = resource.Properties ?? {};\n\n const apiId = props['ApiId'];\n const apiLogicalId = pickRefLogicalId(apiId);\n if (!apiLogicalId) {\n throw new Error(\n `${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): ApiId must be { Ref: '...' } (got ${shortJson(\n apiId\n )}).`\n );\n }\n\n const routeKey = props['RouteKey'];\n if (typeof routeKey !== 'string' || routeKey.length === 0) {\n throw new Error(\n `${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): RouteKey must be a string`\n );\n }\n const apiCdkPath = readApiCdkPath(apiLogicalId, template);\n\n // WebSocket-protocol APIs (#462) are handled by the sibling\n // `discoverWebSocketApis` pipeline — they use route-key dispatch\n // (`$connect` / `$disconnect` / `$default` / custom string) rather\n // than method+path, and have their own upgrade-event server attached\n // to the underlying http.Server. Emit no entry here so the HTTP\n // route table stays free of stub `[501 Not Implemented]` rows for\n // routes the WebSocket pipeline owns. The WebSocket discovery still\n // rejects malformed WebSocket templates with a warn so users see\n // discovery failures.\n const apiResource = template.Resources?.[apiLogicalId];\n if (apiResource?.Type === 'AWS::ApiGatewayV2::Api') {\n const protocolType = (apiResource.Properties ?? {})['ProtocolType'];\n if (protocolType === 'WEBSOCKET') {\n return [];\n }\n }\n\n // RouteKey grammar: `<METHOD> <path>` or `$default`.\n const { method, pathPattern } = parseRouteKey(routeKey);\n const baseRoute: Omit<DiscoveredRoute, 'lambdaLogicalId'> = {\n method,\n pathPattern,\n source: 'http-api',\n apiVersion: 'v2',\n stage: '$default',\n apiLogicalId,\n apiStackName: stackName,\n ...(apiCdkPath !== undefined && { apiCdkPath }),\n declaredAt: `${stackName}/${logicalId}`,\n };\n\n // Resolve the Target — `Target: 'integrations/<integrationLogicalId>'`.\n // CDK emits this as `Fn::Join: ['/', ['integrations', { Ref: <id> }]]`.\n const target = props['Target'];\n const integrationLogicalId = parseHttpApiTargetIntegration(\n target,\n `${stackName}/${logicalId}.Target`\n );\n\n const integration = template.Resources?.[integrationLogicalId];\n if (!integration || integration.Type !== 'AWS::ApiGatewayV2::Integration') {\n throw new Error(\n `${stackName}/${logicalId} (AWS::ApiGatewayV2::Route): Target points at '${integrationLogicalId}' which is not an AWS::ApiGatewayV2::Integration`\n );\n }\n const integrationProps = integration.Properties ?? {};\n\n // C9: filter to AWS_PROXY + no IntegrationSubtype. Both become\n // deferred-error unsupported routes (boot proceeds; 501 at request time).\n const integrationType = integrationProps['IntegrationType'];\n if (integrationType !== 'AWS_PROXY') {\n return [\n {\n ...baseRoute,\n lambdaLogicalId: '',\n unsupported: {\n reason: `${stackName}/${logicalId}: HTTP API v2 integration type '${String(\n integrationType\n )}' is not supported (only AWS_PROXY).`,\n },\n },\n ];\n }\n if (integrationProps['IntegrationSubtype'] !== undefined) {\n return [\n classifyServiceIntegrationRoute(\n baseRoute,\n integrationProps,\n stackName,\n logicalId,\n integrationLogicalId\n ),\n ];\n }\n\n const arnOutcome = resolveLambdaArnOutcome(integrationProps['IntegrationUri']);\n if (arnOutcome.kind === 'unsupported') {\n return [\n {\n ...baseRoute,\n lambdaLogicalId: '',\n unsupported: {\n reason: `${stackName}/${integrationLogicalId}.IntegrationUri: ${arnOutcome.detail} (got ${shortJson(\n integrationProps['IntegrationUri']\n )}). Lambda Arn intrinsics on cross-stack / imported references are not resolvable locally.`,\n },\n },\n ];\n }\n\n return [\n {\n ...baseRoute,\n lambdaLogicalId: arnOutcome.logicalId,\n },\n ];\n}\n\n/**\n * Classify an HTTP API v2 route whose `IntegrationSubtype` is set\n * (service integration, no Lambda backing). Recognized subtypes\n * become `serviceIntegration` routes the HTTP server dispatches via\n * the SDK adapter table in `httpv2-service-integration.ts`;\n * unrecognized subtypes (typo / future-AWS-subtype-cdk-local-doesn't-bundle\n * an SDK for) become deferred-501 unsupported routes.\n *\n * `RequestParameters` is the load-bearing field for dispatch — it\n * carries the SDK input as a flat map keyed by SDK parameter name.\n * Missing / non-object RequestParameters surfaces as unsupported (the\n * SDK call would have nothing to send).\n */\nfunction classifyServiceIntegrationRoute(\n baseRoute: Omit<DiscoveredRoute, 'lambdaLogicalId'>,\n integrationProps: Record<string, unknown>,\n stackName: string,\n routeLogicalId: string,\n integrationLogicalId: string\n): DiscoveredRoute {\n const subtypeRaw = integrationProps['IntegrationSubtype'];\n const declaredAt = `${stackName}/${routeLogicalId}`;\n\n if (!isSupportedSubtype(subtypeRaw)) {\n return {\n ...baseRoute,\n lambdaLogicalId: '',\n unsupported: {\n reason: `${declaredAt}: HTTP API v2 service integration subtype '${stringifyValue(\n subtypeRaw\n )}' is not supported by ${getEmbedConfig().cliName} start-api (see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-aws-services-reference.html for the supported list).`,\n },\n };\n }\n\n const requestParameters = integrationProps['RequestParameters'];\n if (\n !requestParameters ||\n typeof requestParameters !== 'object' ||\n Array.isArray(requestParameters)\n ) {\n return {\n ...baseRoute,\n lambdaLogicalId: '',\n unsupported: {\n reason: `${stackName}/${integrationLogicalId}: HTTP API v2 service integration '${subtypeRaw}' is missing RequestParameters or it is not an object — cannot dispatch to the SDK without input mapping.`,\n },\n };\n }\n\n const responseParameters = integrationProps['ResponseParameters'];\n const validatedResponseParameters =\n responseParameters &&\n typeof responseParameters === 'object' &&\n !Array.isArray(responseParameters)\n ? (responseParameters as Record<string, Record<string, string>>)\n : undefined;\n\n return {\n ...baseRoute,\n lambdaLogicalId: '',\n serviceIntegration: {\n subtype: subtypeRaw as SupportedSubtype,\n requestParameters: requestParameters as Record<string, unknown>,\n ...(validatedResponseParameters && { responseParameters: validatedResponseParameters }),\n },\n };\n}\n\n/**\n * Discover the synthetic `ANY /{proxy+}` route from an\n * `AWS::Lambda::Url` resource.\n *\n * Per-shape classification:\n * - `AuthType === 'NONE'` + `InvokeMode === 'BUFFERED'` (or unset) → normal route.\n * - `AuthType === 'NONE'` + `InvokeMode === 'RESPONSE_STREAM'` → normal route\n * dispatched via the RIE streaming protocol (the response body is a\n * JSON prelude — `{statusCode, headers, cookies?}` — followed by 8\n * NULL bytes and then the raw body chunks). The HTTP server pipes\n * the chunks to the client with `Transfer-Encoding: chunked` (#467).\n * - `AuthType === 'AWS_IAM'` → normal route. The `IamAuthorizer` is\n * attached at the authorizer-resolver pass (`detectAuthorizer` in\n * `authorizer-resolver.ts`) so the HTTP server runs the same SigV4\n * verification it ships for REST v1 `AuthorizationType: 'AWS_IAM'`\n * (PR #447). Signature verification only — no IAM policy emulation.\n * - `AuthType` other than `'NONE'` / `'AWS_IAM'` (defensive — AWS docs\n * only define those two values) → deferred-error unsupported. Boot\n * proceeds; HTTP 501 + `reason` at request time.\n *\n * The Lambda Arn intrinsic resolution still **hard-errors** when it\n * cannot pin down a same-template Lambda — Function URLs have no other\n * identifying info (no RouteKey / RestApi parent), so the route would\n * be uninformative as a deferred-501 entry.\n */\nfunction discoverFunctionUrl(\n logicalId: string,\n resource: TemplateResource,\n template: CloudFormationTemplate,\n stackName: string\n): DiscoveredRoute[] {\n const props = resource.Properties ?? {};\n\n // Resolve the backing Lambda first — without it we cannot identify\n // the route surface at all. An unresolvable Arn becomes a hard error\n // because Function URLs have no other identifying info (no RouteKey /\n // RestApi parent).\n const targetArn = props['TargetFunctionArn'];\n const arnOutcome = resolveLambdaArnOutcome(targetArn);\n if (arnOutcome.kind === 'unsupported') {\n throw new Error(\n `${stackName}/${logicalId}.TargetFunctionArn: ${arnOutcome.detail} (got ${shortJson(targetArn)}).`\n );\n }\n const lambdaLogicalId = arnOutcome.logicalId;\n // Function URLs identify by their backing Lambda — surface the Lambda's\n // cdk path so `--api MyStack/MyHandler` (the natural CDK Construct path\n // for the Function, not the auto-generated URL child) matches.\n const lambdaCdkPath = readApiCdkPath(lambdaLogicalId, template);\n const baseRoute: Omit<DiscoveredRoute, 'lambdaLogicalId'> = {\n method: 'ANY',\n pathPattern: '/{proxy+}',\n source: 'function-url',\n apiVersion: 'v2',\n stage: '$default',\n apiStackName: stackName,\n // Issue #644: set `apiLogicalId` to the AWS::Lambda::Url's own\n // logical ID so the CORS preflight interceptor can look up the\n // Function URL's `Cors` block via `buildCorsConfigByApiId`. The\n // pre-fix design left this undefined (\"Function URLs aren't\n // grouped under a parent API\"); the new semantics is that\n // `apiLogicalId` points at the CORS-config-bearing resource\n // (HTTP API v2's `AWS::ApiGatewayV2::Api` OR Function URL's\n // `AWS::Lambda::Url`), which is the only consumer of the field.\n apiLogicalId: logicalId,\n ...(lambdaCdkPath !== undefined && { apiCdkPath: lambdaCdkPath }),\n declaredAt: `${stackName}/${logicalId}`,\n };\n\n // AuthType: AWS_IAM is supported via the same SigV4 verification path\n // that REST v1 `AuthorizationType: 'AWS_IAM'` uses (PR #447). The\n // `IamAuthorizer` descriptor is attached later by `detectAuthorizer`\n // in `authorizer-resolver.ts` — no per-route field set here. Anything\n // other than the two AWS-documented values (`NONE` / `AWS_IAM`) is\n // rejected as deferred-error unsupported so a future API shape change\n // surfaces at request time rather than silently mis-routing.\n const authType = props['AuthType'];\n if (authType !== 'NONE' && authType !== 'AWS_IAM') {\n return [\n {\n ...baseRoute,\n lambdaLogicalId,\n unsupported: {\n reason: `${stackName}/${logicalId}: AuthType ${shortJson(\n authType\n )} is not a recognized Function URL auth type (expected 'NONE' or 'AWS_IAM').`,\n },\n },\n ];\n }\n // InvokeMode controls the response wire protocol. RESPONSE_STREAM\n // opts into the RIE streaming protocol (JSON prelude + 8-NULL-byte\n // separator + raw chunked body) — the local server invokes the Lambda\n // with the `Lambda-Runtime-Function-Response-Mode: streaming` request\n // header and pipes the chunks to the HTTP client with\n // `Transfer-Encoding: chunked`. Closes issue #467. Anything other\n // than these two AWS-documented values is rejected so a future API\n // shape change surfaces as a clear error rather than silent fallback.\n const invokeModeRaw = props['InvokeMode'];\n let invokeMode: 'BUFFERED' | 'RESPONSE_STREAM' = 'BUFFERED';\n if (invokeModeRaw === 'RESPONSE_STREAM') {\n invokeMode = 'RESPONSE_STREAM';\n } else if (invokeModeRaw !== undefined && invokeModeRaw !== 'BUFFERED') {\n // Render with shortJson so an object-valued InvokeMode (defensive\n // — AWS docs require a string, but a malformed template could ship\n // an intrinsic) shows as JSON rather than `[object Object]`.\n return [\n {\n ...baseRoute,\n lambdaLogicalId,\n unsupported: {\n reason: `${stackName}/${logicalId}: InvokeMode ${shortJson(\n invokeModeRaw\n )} is not a recognized value (expected 'BUFFERED' or 'RESPONSE_STREAM').`,\n },\n },\n ];\n }\n\n return [\n {\n ...baseRoute,\n lambdaLogicalId,\n invokeMode,\n },\n ];\n}\n\n/**\n * Read the `aws:cdk:path` metadata of the resource at `logicalId`,\n * returning the empty string when the resource is missing or the\n * metadata isn't set. Hides the \"may be missing for a hand-rolled\n * `cfn.Resource`\" branch from every call site.\n */\nfunction readApiCdkPath(logicalId: string, template: CloudFormationTemplate): string | undefined {\n const resource = template.Resources?.[logicalId];\n if (!resource) return undefined;\n const path = readCdkPath(resource);\n return path || undefined;\n}\n\n/**\n * Local intrinsic resolver for `IntegrationUri` (and the equivalent\n * `Uri` field on REST v1 Method.Integration). Delegates to the shared\n * `resolveLambdaArnIntrinsic` in `intrinsic-lambda-arn.ts` (extracted in\n * issue #286 Gaps 3 / 4); see that module's docstring for the full\n * shape list — `Ref` / `Fn::GetAtt: [..., 'Arn']` / the REST v1\n * invoke-ARN `Fn::Join` wrapper / the `Fn::Sub` invoke-ARN wrapper (both\n * 1-arg and 2-arg forms).\n *\n * Non-throwing: returns the shared resolver's discriminated union\n * unchanged so each call site can decide whether to surface the\n * unsupported case as a per-route `unsupported` flag (the new default)\n * or as a hard error (Function URLs, which lack route-level identity\n * without their Lambda).\n *\n * **Why we don't reuse `src/deployment/intrinsic-function-resolver.ts`**:\n * that resolver is deploy-state-coupled — it pulls in STS / EC2 / Secrets\n * Manager / SSM SDKs and the state backend to resolve runtime values.\n * `cdkl start-api` runs purely against the synthesized template\n * and doesn't have any of that.\n */\nfunction resolveLambdaArnOutcome(\n value: unknown\n): { kind: 'resolved'; logicalId: string } | { kind: 'unsupported'; detail: string } {\n return resolveLambdaArnShared(value);\n}\n\n/**\n * Marker prefix the HTTP API v2 Route `Target` field always starts with —\n * AWS documents this as `integrations/<IntegrationId>`. Load-bearing\n * signal that an `Fn::Sub` shape on this field is actually pointing at\n * a same-template Integration rather than something unrelated.\n */\nconst TARGET_INTEGRATIONS_PREFIX = 'integrations/';\n\n/**\n * Parse an HTTP API Route's `Target` into the integration's logical ID.\n *\n * CDK emits one of:\n * - `Fn::Join: ['/', ['integrations', { Ref: 'MyIntegration' }]]` (rare).\n * - `Fn::Join: ['', ['integrations/', { Ref: 'MyIntegration' }]]`\n * (the shape `aws-cdk-lib/aws-apigatewayv2`'s `HttpApi.addRoutes`\n * actually emits — empty separator + `'integrations/'` literal\n * prefix in front of the Ref).\n * - `Fn::Sub: 'integrations/${MyIntegration}'` (1-arg form — AWS-docs\n * canonical; emitted by hand-rolled `CfnRoute` constructs).\n * - `Fn::Sub: ['integrations/${IntId}', { IntId: <Ref|GetAtt> }]`\n * (2-arg form — what `cdk.Fn.sub(template, vars)` synthesizes\n * when users build `target` programmatically).\n * - `'integrations/abc123'` (literal — rare).\n *\n * All five forms are accepted; anything else throws.\n */\nfunction parseHttpApiTargetIntegration(target: unknown, location: string): string {\n if (typeof target === 'string') {\n const m = /^integrations\\/(.+)$/.exec(target);\n if (m) return m[1]!;\n throw new Error(`${location}: literal Target '${target}' must start with 'integrations/'`);\n }\n if (target && typeof target === 'object' && !Array.isArray(target)) {\n const obj = target as Record<string, unknown>;\n\n const join = obj['Fn::Join'];\n if (Array.isArray(join) && join.length === 2 && Array.isArray(join[1])) {\n const sep: unknown = join[0];\n const parts = join[1] as unknown[];\n\n // Slash-separated form: ['/', ['integrations', { Ref }]]\n if (sep === '/' && parts.length === 2 && parts[0] === 'integrations') {\n const ref = pickRefLogicalId(parts[1]);\n if (ref) return ref;\n }\n\n // Empty-separator form: ['', ['integrations/', { Ref }]]\n if (sep === '' && parts.length === 2 && parts[0] === 'integrations/') {\n const ref = pickRefLogicalId(parts[1]);\n if (ref) return ref;\n }\n }\n\n if ('Fn::Sub' in obj) {\n const sub = obj['Fn::Sub'];\n\n // 1-arg form: `'integrations/${LogicalId}'` — the placeholder name\n // is a direct `Ref` to the integration resource. The marker prefix\n // is load-bearing: a `Fn::Sub` without `integrations/` is not a\n // route Target shape and the caller should see the same hard error\n // as any other bad input.\n if (typeof sub === 'string') {\n const m = new RegExp(`^${TARGET_INTEGRATIONS_PREFIX}\\\\$\\\\{([^}]+)\\\\}$`).exec(sub);\n if (m) {\n const placeholder = m[1]!;\n // Reject dotted refs (`${LogicalId.attr}`) — Integration has no\n // GetAtt shape that produces a route-Target id.\n if (!placeholder.includes('.')) return placeholder;\n }\n }\n\n // 2-arg form: `['integrations/${Var}', { Var: { Ref: 'LogicalId' } }]`\n // — the template references a binding whose value resolves to the\n // integration logical id.\n if (\n Array.isArray(sub) &&\n sub.length === 2 &&\n typeof sub[0] === 'string' &&\n sub[1] !== null &&\n typeof sub[1] === 'object' &&\n !Array.isArray(sub[1])\n ) {\n const template = sub[0];\n const bindings = sub[1] as Record<string, unknown>;\n const m = new RegExp(`^${TARGET_INTEGRATIONS_PREFIX}\\\\$\\\\{([^}]+)\\\\}$`).exec(template);\n if (m) {\n const placeholder = m[1]!;\n const bound = bindings[placeholder];\n if (bound !== undefined) {\n const ref = pickRefLogicalId(bound);\n if (ref) return ref;\n }\n }\n }\n }\n }\n throw new Error(\n `${location}: Target must be 'integrations/<id>', Fn::Join with one of the documented shapes, or Fn::Sub with an 'integrations/\\${...}' template (got ${shortJson(\n target\n )}).`\n );\n}\n\n/**\n * Parse an HTTP API RouteKey (`'<METHOD> <path>'` or `'$default'`) into\n * its components.\n */\nfunction parseRouteKey(routeKey: string): { method: string; pathPattern: string } {\n if (routeKey === '$default') {\n return { method: 'ANY', pathPattern: '$default' };\n }\n const m = /^([A-Za-z]+)\\s+(\\S+)$/.exec(routeKey);\n if (!m) {\n throw new Error(\n `RouteKey '${routeKey}' is malformed: expected '<METHOD> <path>' (e.g. 'GET /items/{id}') or '$default'.`\n );\n }\n return { method: m[1]!.toUpperCase(), pathPattern: m[2]! };\n}\n\n/**\n * Compact JSON for error messages — caps long objects so a malformed\n * intrinsic doesn't dump the whole template into a stderr line.\n */\nfunction shortJson(value: unknown): string {\n try {\n const s = JSON.stringify(value);\n return s.length > 200 ? `${s.slice(0, 200)}…` : s;\n } catch {\n return stringifyValue(value);\n }\n}\n","import { readCdkPath } from '../cli/cdk-path.js';\nimport type { StackInfo } from '../synthesis/assembly-reader.js';\nimport type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { RouteDiscoveryError } from '../utils/error-handler.js';\nimport { resolveLambdaArnIntrinsic } from './intrinsic-lambda-arn.js';\nimport { pickRefLogicalId } from './intrinsic-utils.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Discovered WebSocket API for `cdkl start-api`.\n *\n * AWS WebSocket APIs use a fundamentally different model from HTTP APIs:\n * routes are keyed by an opaque `RouteKey` string (`$connect`,\n * `$disconnect`, `$default`, or a custom user-defined string), NOT by\n * (method, path) tuples; the API's `RouteSelectionExpression` decides\n * which custom route fires for any given client message. We therefore\n * surface WebSocket discovery as a separate type alongside the\n * `DiscoveredRoute[]` produced by HTTP / REST / Function URL discovery\n * (see design Q4 in docs/design/462-websocket-api.md).\n *\n * Filter: only `AWS::ApiGatewayV2::Api` resources with\n * `ProtocolType: 'WEBSOCKET'` are discovered as WebSocket APIs. HTTP\n * API v2 (`ProtocolType: 'HTTP'`) continues through the normal\n * `discoverRoutes` path.\n */\nexport interface DiscoveredWebSocketApi {\n /** Logical ID of the parent `AWS::ApiGatewayV2::Api` resource. */\n apiLogicalId: string;\n /** Stack name containing the API resource. */\n apiStackName: string;\n /** Diagnostic only — `<StackName>/<LogicalId>`. */\n declaredAt: string;\n /**\n * CDK Construct path (`aws:cdk:path` metadata) of the API resource.\n * Populated when the synthesized resource carries the metadata. Used\n * by `--api` to accept the CDK display path form.\n */\n apiCdkPath?: string;\n /**\n * The API's selection expression. Defaults to the AWS-canonical\n * `$request.body.action` when the property is omitted. v1 supports\n * only the `$request.body.<key>` (optionally nested) shape; any\n * other shape (`$request.header.X`, `$context.X`) hard-errors at\n * discovery time.\n */\n routeSelectionExpression: string;\n /**\n * Resolved Stage name. Picked from the first `AWS::ApiGatewayV2::Stage`\n * referencing this API; falls back to `'local'` when no Stage is\n * attached (defensive — CDK's `WebSocketStage` always emits one,\n * but a hand-rolled template might not).\n */\n stage: string;\n /**\n * Discovered routes keyed by RouteKey. Each entry resolves the\n * route's backing Lambda via its\n * `AWS::ApiGatewayV2::Integration.IntegrationUri`.\n *\n * v1 supports `IntegrationType: 'AWS_PROXY'` only. Non-Lambda\n * integrations (service-direct) are rejected at discovery — there is\n * no analog of the deferred-501 fallback for WebSocket because the\n * full API model is route-keyed and a single unsupported route\n * forces a different selection-expression evaluator. Closes the\n * design's \"Lambda-only in v1\" constraint.\n */\n routes: WebSocketRouteEntry[];\n /**\n * Set when the whole API is unsupported by cdk-local's local emulation.\n * The CLI's WebSocket attach loop skips an `unsupported`-tagged API\n * (no server is attached, no upgrade is accepted) and surfaces the\n * `reason` as a startup warn naming the affected API so the user\n * sees the gap BEFORE attempting a `wscat`.\n *\n * v1 sets this when any route declares\n * `AuthorizationType !== 'NONE'` (`'AWS_IAM'` / `'CUSTOM'` /\n * `'JWT'` / `'COGNITO_USER_POOLS'`) — auth on `$connect` is the\n * canonical WebSocket guard and silently admitting unauthenticated\n * clients would be a security gap (mirrors the structural pre-empt\n * fix PR #514 shipped for HTTP API v2 service integrations). Full\n * authorizer support (wiring `attachAuthorizers` / `runAuthorizerPass`\n * into the `$connect` flow) is tracked as a follow-up.\n */\n unsupported?: { reason: string };\n}\n\n/** One row in {@link DiscoveredWebSocketApi.routes}. */\nexport interface WebSocketRouteEntry {\n /** `$connect` / `$disconnect` / `$default` / custom user-defined key. */\n routeKey: string;\n /** Logical ID of the backing Lambda. */\n targetLambdaLogicalId: string;\n /** Stack the backing Lambda lives in. */\n lambdaStackName: string;\n /** Diagnostic only — `<StackName>/<RouteLogicalId>`. */\n declaredAt: string;\n}\n\nconst DEFAULT_ROUTE_SELECTION_EXPRESSION = '$request.body.action';\nconst DEFAULT_STAGE = 'local';\n\n/**\n * Walk every synthesized stack and produce one {@link DiscoveredWebSocketApi}\n * per WebSocket API found. Errors per API are aggregated and surfaced\n * as a single {@link RouteDiscoveryError} (matches the HTTP-side\n * discovery behavior — a single malformed API shouldn't abort the\n * server boot for sibling APIs).\n *\n * Resolution chain:\n * 1. Find each `AWS::ApiGatewayV2::Api` with `ProtocolType: WEBSOCKET`.\n * 2. Validate `RouteSelectionExpression` (only `$request.body.<key>`\n * forms supported in v1).\n * 3. Resolve attached Stage (first Stage referencing this API).\n * 4. Walk every `AWS::ApiGatewayV2::Route` referencing this API,\n * resolve each route's Target → Integration → IntegrationUri →\n * Lambda logical ID via the shared `resolveLambdaArnIntrinsic`\n * helper (handles every CFn intrinsic shape CDK emits).\n */\nexport function discoverWebSocketApis(stacks: readonly StackInfo[]): {\n apis: DiscoveredWebSocketApi[];\n errors: string[];\n} {\n const apis: DiscoveredWebSocketApi[] = [];\n const errors: string[] = [];\n\n for (const stack of stacks) {\n const template = stack.template;\n const resources = template.Resources ?? {};\n\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ApiGatewayV2::Api') continue;\n const props = resource.Properties ?? {};\n if (props['ProtocolType'] !== 'WEBSOCKET') continue;\n\n try {\n apis.push(discoverOneApi(logicalId, resource, template, stack.stackName));\n } catch (err) {\n errors.push(err instanceof Error ? err.message : String(err));\n }\n }\n }\n\n return { apis, errors };\n}\n\n/**\n * Convenience wrapper around {@link discoverWebSocketApis} that\n * throws on any error (mirrors `discoverRoutes` for HTTP — useful in\n * test fixtures where errors should fail fast).\n */\nexport function discoverWebSocketApisOrThrow(\n stacks: readonly StackInfo[]\n): DiscoveredWebSocketApi[] {\n const { apis, errors } = discoverWebSocketApis(stacks);\n if (errors.length > 0) {\n throw new RouteDiscoveryError(\n `${getEmbedConfig().cliName} start-api: ${errors.length} malformed WebSocket API(s) in the synthesized template:\\n` +\n errors.map((e) => ` - ${e}`).join('\\n')\n );\n }\n return apis;\n}\n\nfunction discoverOneApi(\n logicalId: string,\n resource: TemplateResource,\n template: CloudFormationTemplate,\n stackName: string\n): DiscoveredWebSocketApi {\n const props = resource.Properties ?? {};\n const declaredAt = `${stackName}/${logicalId}`;\n\n const rawSelection = props['RouteSelectionExpression'];\n const routeSelectionExpression =\n typeof rawSelection === 'string' && rawSelection.length > 0\n ? rawSelection\n : DEFAULT_ROUTE_SELECTION_EXPRESSION;\n\n // v1 supports only `$request.body.<key>` (optionally `<nested.key>`)\n // selection expressions. Reject anything else at discovery time\n // rather than fail mid-message with a confusing error.\n assertSupportedSelectionExpression(routeSelectionExpression, declaredAt);\n\n const stage = pickStage(logicalId, template);\n const apiCdkPath = readApiCdkPath(logicalId, template);\n const routes = collectRoutesForApi(logicalId, template, stackName);\n\n if (routes.length === 0) {\n throw new Error(\n `${declaredAt}: WebSocket API has no AWS::ApiGatewayV2::Route children — at least one route (typically '$connect') is required to dispatch.`\n );\n }\n\n // Scan for non-NONE AuthorizationType on any Route belonging to\n // this API. If found, tag the whole API as unsupported — the CLI\n // attach loop will skip it (no upgrade accepted) and surface the\n // affected routes as a startup warn. cdkl v1 does NOT emulate\n // WebSocket authorizers; silently admitting an unauthenticated\n // client would be a security gap that diverges from\n // AWS-deployed behavior. Full authorizer support (wire\n // `attachAuthorizers` / `runAuthorizerPass` into the `$connect`\n // flow) is tracked as a follow-up.\n const authRoutes = collectAuthRoutesForApi(logicalId, template, stackName);\n const unsupported =\n authRoutes.length > 0\n ? {\n reason: `WebSocket API requires authorizer support, which ${getEmbedConfig().binaryName} v1 does not emulate. Affected route(s): ${authRoutes\n .map((r) => `${r.routeKey} [AuthorizationType=${r.authorizationType}]`)\n .join(\n ', '\n )}. The API will be discovered but no upgrade requests will be accepted on this server.`,\n }\n : undefined;\n\n return {\n apiLogicalId: logicalId,\n apiStackName: stackName,\n declaredAt,\n ...(apiCdkPath !== undefined && { apiCdkPath }),\n routeSelectionExpression,\n stage,\n routes,\n ...(unsupported !== undefined && { unsupported }),\n };\n}\n\n/**\n * Scan the synthesized template for every `AWS::ApiGatewayV2::Route`\n * referencing the given WebSocket API, returning the subset whose\n * `AuthorizationType` is set to anything other than `NONE` (the\n * AWS-default when omitted). Used by {@link discoverOneApi} to tag\n * the parent API as unsupported when v1's no-authorizer emulation\n * gap would otherwise let unauthenticated clients through.\n */\nfunction collectAuthRoutesForApi(\n apiLogicalId: string,\n template: CloudFormationTemplate,\n _stackName: string\n): { routeKey: string; authorizationType: string }[] {\n const resources = template.Resources ?? {};\n const result: { routeKey: string; authorizationType: string }[] = [];\n for (const [, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ApiGatewayV2::Route') continue;\n const props = resource.Properties ?? {};\n const parentRef = pickRefLogicalId(props['ApiId']);\n if (parentRef !== apiLogicalId) continue;\n const authType = props['AuthorizationType'];\n // AWS-default when omitted is `NONE`. An absent property is the\n // only safe pass-through path.\n if (authType === undefined) continue;\n const routeKey = props['RouteKey'];\n const routeKeyForReport = typeof routeKey === 'string' ? routeKey : '<unknown>';\n // Fail-closed on any shape we cannot positively prove is `'NONE'`:\n // empty string, `null`, intrinsic-valued (`{Ref: ...}` / `{Fn::Sub: ...}`\n // / other object), or numeric. Silently admitting an\n // intrinsic-valued `AuthorizationType` would re-introduce the\n // pre-fix BLOCKER-B2 security gap for the uncommon-but-valid CDK\n // path that passes a context value through.\n if (typeof authType !== 'string' || authType.length === 0) {\n result.push({\n routeKey: routeKeyForReport,\n authorizationType: '<intrinsic-or-malformed>',\n });\n continue;\n }\n if (authType === 'NONE') continue;\n result.push({\n routeKey: routeKeyForReport,\n authorizationType: authType,\n });\n }\n return result;\n}\n\n/**\n * `$request.body.<key>` is the AWS-canonical shape and the only one\n * v1 supports. Allow nested dot access (`$request.body.action.subKey`)\n * — real CDK chat apps sometimes use this for protocol versioning.\n *\n * Reject array-index access (`$request.body.items[0]`), filter\n * expressions, header / context selections — these would require a\n * fuller JSONPath / VTL evaluator and are out of scope for v1.\n */\nfunction assertSupportedSelectionExpression(expr: string, declaredAt: string): void {\n if (!/^\\$request\\.body(?:\\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(expr)) {\n throw new Error(\n `${declaredAt}: RouteSelectionExpression '${expr}' is not supported in ${getEmbedConfig().cliName} start-api v1 — only '$request.body.<key>' shapes (optionally nested via dots) are recognized. File a follow-up issue if you need '$request.header.X' / '$context.X' / array-index access.`\n );\n }\n}\n\n/**\n * Parse a `$request.body.x.y` selection expression into the JSON-path\n * tokens after `$request.body`. Returns `['x', 'y']` for the example\n * above. Used at message-dispatch time to walk the parsed message body.\n */\nexport function parseSelectionExpressionPath(expr: string): string[] {\n // Strip the `$request.body.` prefix and split on `.`. We already\n // validated the shape in `assertSupportedSelectionExpression`, so a\n // failed match here is a defensive guard.\n const m = /^\\$request\\.body\\.(.+)$/.exec(expr);\n if (!m) return [];\n return m[1]!.split('.');\n}\n\n/**\n * Pick the first `AWS::ApiGatewayV2::Stage` referencing the API. CDK's\n * `apigatewayv2.WebSocketStage` always emits one; the fallback to\n * `'local'` handles hand-rolled templates without a Stage.\n */\nfunction pickStage(apiLogicalId: string, template: CloudFormationTemplate): string {\n const resources = template.Resources ?? {};\n for (const [, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ApiGatewayV2::Stage') continue;\n const props = resource.Properties ?? {};\n const ref = pickRefLogicalId(props['ApiId']);\n if (ref === apiLogicalId) {\n const stageName = props['StageName'];\n if (typeof stageName === 'string' && stageName.length > 0) return stageName;\n }\n }\n return DEFAULT_STAGE;\n}\n\n/**\n * Walk every `AWS::ApiGatewayV2::Route` and resolve each one whose\n * parent `ApiId` Ref matches the WebSocket API. Per-route failures\n * abort the API's discovery (a partial route map would silently\n * disable some routes — better to fail fast and let the user fix the\n * template).\n */\nfunction collectRoutesForApi(\n apiLogicalId: string,\n template: CloudFormationTemplate,\n stackName: string\n): WebSocketRouteEntry[] {\n const resources = template.Resources ?? {};\n const result: WebSocketRouteEntry[] = [];\n const seenKeys = new Set<string>();\n\n for (const [routeLogicalId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ApiGatewayV2::Route') continue;\n const props = resource.Properties ?? {};\n const parentRef = pickRefLogicalId(props['ApiId']);\n if (parentRef !== apiLogicalId) continue;\n\n const declaredAt = `${stackName}/${routeLogicalId}`;\n const routeKey = props['RouteKey'];\n if (typeof routeKey !== 'string' || routeKey.length === 0) {\n throw new Error(`${declaredAt}: RouteKey must be a non-empty string.`);\n }\n if (seenKeys.has(routeKey)) {\n throw new Error(\n `${declaredAt}: WebSocket API has duplicate RouteKey '${routeKey}' — each RouteKey may appear at most once per API.`\n );\n }\n seenKeys.add(routeKey);\n\n const targetLogicalId = parseRouteTarget(props['Target'], declaredAt);\n const integration = resources[targetLogicalId];\n if (!integration || integration.Type !== 'AWS::ApiGatewayV2::Integration') {\n throw new Error(\n `${declaredAt}: Target points at '${targetLogicalId}' which is not an AWS::ApiGatewayV2::Integration.`\n );\n }\n const integrationProps = integration.Properties ?? {};\n const integrationType = integrationProps['IntegrationType'];\n if (integrationType !== 'AWS_PROXY') {\n throw new Error(\n `${declaredAt}: WebSocket route IntegrationType '${String(\n integrationType\n )}' is not supported in ${getEmbedConfig().cliName} start-api v1 — only AWS_PROXY (Lambda) integrations are emulated.`\n );\n }\n\n const arnOutcome = resolveLambdaArnIntrinsic(integrationProps['IntegrationUri']);\n if (arnOutcome.kind === 'unsupported') {\n throw new Error(\n `${stackName}/${targetLogicalId}.IntegrationUri: ${arnOutcome.detail} — WebSocket routes must point at a same-template Lambda.`\n );\n }\n\n result.push({\n routeKey,\n targetLambdaLogicalId: arnOutcome.logicalId,\n lambdaStackName: stackName,\n declaredAt,\n });\n }\n\n return result;\n}\n\n/**\n * WebSocket Routes use the same `Target: 'integrations/<id>'` shape as\n * HTTP API v2 Routes. We accept the same five forms documented in\n * `route-discovery.ts:parseHttpApiTargetIntegration` — literal string,\n * two `Fn::Join` shapes, two `Fn::Sub` shapes.\n *\n * Implementation note: we intentionally duplicate the parser rather\n * than reach into `route-discovery.ts` because that module is in flux\n * (`unsupported` / `mockCors` shapes; HTTP-specific). When the two\n * parsers grow apart, the WebSocket one only needs to track the AWS\n * WebSocket-side shape — which has been stable since 2018.\n */\nfunction parseRouteTarget(target: unknown, location: string): string {\n if (typeof target === 'string') {\n const m = /^integrations\\/(.+)$/.exec(target);\n if (m) return m[1]!;\n throw new Error(`${location}: literal Target '${target}' must start with 'integrations/'.`);\n }\n if (target && typeof target === 'object' && !Array.isArray(target)) {\n const obj = target as Record<string, unknown>;\n\n const join = obj['Fn::Join'];\n if (Array.isArray(join) && join.length === 2 && Array.isArray(join[1])) {\n const sep: unknown = join[0];\n const parts = join[1] as unknown[];\n\n if (sep === '/' && parts.length === 2 && parts[0] === 'integrations') {\n const ref = pickRefLogicalId(parts[1]);\n if (ref) return ref;\n }\n if (sep === '' && parts.length === 2 && parts[0] === 'integrations/') {\n const ref = pickRefLogicalId(parts[1]);\n if (ref) return ref;\n }\n }\n\n if ('Fn::Sub' in obj) {\n const sub = obj['Fn::Sub'];\n const prefix = 'integrations/';\n if (typeof sub === 'string') {\n const m = new RegExp(`^${prefix}\\\\$\\\\{([^}]+)\\\\}$`).exec(sub);\n if (m) {\n const placeholder = m[1]!;\n if (!placeholder.includes('.')) return placeholder;\n }\n }\n if (\n Array.isArray(sub) &&\n sub.length === 2 &&\n typeof sub[0] === 'string' &&\n sub[1] !== null &&\n typeof sub[1] === 'object' &&\n !Array.isArray(sub[1])\n ) {\n const template = sub[0];\n const bindings = sub[1] as Record<string, unknown>;\n const m = new RegExp(`^${prefix}\\\\$\\\\{([^}]+)\\\\}$`).exec(template);\n if (m) {\n const placeholder = m[1]!;\n const bound = bindings[placeholder];\n if (bound !== undefined) {\n const ref = pickRefLogicalId(bound);\n if (ref) return ref;\n }\n }\n }\n }\n }\n throw new Error(\n `${location}: Target must be 'integrations/<id>' literal, Fn::Join with the documented shapes, or Fn::Sub with an 'integrations/\\${...}' template.`\n );\n}\n\nfunction readApiCdkPath(logicalId: string, template: CloudFormationTemplate): string | undefined {\n const resource = template.Resources?.[logicalId];\n if (!resource) return undefined;\n const path = readCdkPath(resource);\n return path === '' ? undefined : path;\n}\n","import { readCdkPathOrUndefined } from '../cli/cdk-path.js';\nimport type { StackInfo } from '../synthesis/assembly-reader.js';\nimport { getLogger } from '../utils/logger.js';\nimport { AGENTCORE_RUNTIME_TYPE } from './agentcore-resolver.js';\nimport { discoverRoutes } from './route-discovery.js';\nimport { discoverWebSocketApis } from './websocket-route-discovery.js';\n\n/**\n * One runnable target surfaced by `cdkl list`.\n *\n * Both addressable forms the four `cdkl` commands accept are carried so\n * the user can copy either: the stack-qualified logical ID\n * ({@link qualifiedId}, valid in every app) and — when the synthesized\n * resource carries `aws:cdk:path` metadata — the CDK Construct display\n * path ({@link displayPath}).\n */\nexport interface TargetEntry {\n /** CloudFormation logical ID (e.g. `OrdersServiceB12`). */\n logicalId: string;\n /** Physical stack name the resource lives in. */\n stackName: string;\n /**\n * Stack-qualified logical ID (`<stackName>:<logicalId>`) — a target\n * form accepted by every command, including multi-stack apps.\n */\n qualifiedId: string;\n /**\n * CDK Construct display path with a trailing `/Resource` stripped\n * (e.g. `MyStack/OrdersService`). Omitted when the resource carries no\n * `aws:cdk:path` metadata (hand-rolled `CfnResource`s).\n */\n displayPath?: string;\n /**\n * Human-readable surface kind, only set for `apis` entries\n * (`REST API v1` / `HTTP API v2` / `Function URL` / `WebSocket`). Lets\n * the interactive picker and `cdkl list` tell otherwise-similar API\n * targets apart. Omitted for Lambda / ECS targets.\n */\n kind?: string;\n}\n\n/**\n * Runnable targets discovered in a synthesized CDK app, grouped by the\n * command that consumes them.\n */\nexport interface TargetListing {\n /** `AWS::Lambda::Function` — `cdkl invoke`. */\n lambdas: TargetEntry[];\n /**\n * API surfaces `cdkl start-api` can serve — REST v1, HTTP API v2,\n * Function URLs, and WebSocket APIs. Each entry is one API surface\n * (de-duplicated across its routes).\n */\n apis: TargetEntry[];\n /** `AWS::ECS::Service` — `cdkl start-service`. */\n ecsServices: TargetEntry[];\n /** `AWS::ECS::TaskDefinition` — `cdkl run-task`. */\n ecsTaskDefinitions: TargetEntry[];\n /** `AWS::BedrockAgentCore::Runtime` — `cdkl invoke-agentcore`. */\n agentCoreRuntimes: TargetEntry[];\n /** Application `AWS::ElasticLoadBalancingV2::LoadBalancer` — `cdkl start-alb`. */\n loadBalancers: TargetEntry[];\n}\n\nfunction makeEntry(\n stackName: string,\n logicalId: string,\n cdkPath: string | undefined,\n kind?: string\n): TargetEntry {\n const entry: TargetEntry = {\n logicalId,\n stackName,\n qualifiedId: `${stackName}:${logicalId}`,\n };\n // Mirror `cdkl invoke`'s own display-path suggestion: the L1 child a\n // CDK L2 construct emits carries a trailing `/Resource` the target\n // syntax does not need (the prefix rule matches the L2 ancestor).\n const display = cdkPath ? cdkPath.replace(/\\/Resource$/, '') : undefined;\n if (display) entry.displayPath = display;\n if (kind) entry.kind = kind;\n return entry;\n}\n\n/** Map a discovered route's `source` to the human-readable surface kind. */\nfunction apiKindLabel(source: 'http-api' | 'rest-v1' | 'function-url'): string {\n switch (source) {\n case 'http-api':\n return 'HTTP API v2';\n case 'rest-v1':\n return 'REST API v1';\n case 'function-url':\n return 'Function URL';\n }\n}\n\nfunction scanByType(stacks: readonly StackInfo[], type: string): TargetEntry[] {\n const entries: TargetEntry[] = [];\n for (const stack of stacks) {\n const resources = stack.template.Resources ?? {};\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type !== type) continue;\n entries.push(makeEntry(stack.stackName, logicalId, readCdkPathOrUndefined(resource)));\n }\n }\n return entries;\n}\n\n/**\n * Enumerate the API surfaces `cdkl start-api` can serve.\n *\n * Reuses the same discovery `start-api` runs (REST v1 / HTTP API v2 /\n * Function URL routes + WebSocket APIs) so the listed identifiers are\n * exactly the ones the `[target]` filter accepts. For a Function URL that\n * means the backing Lambda's logical ID and `aws:cdk:path` — start-api\n * addresses a Function URL by its backing Lambda, not by the URL\n * resource (see `routeMatchesIdentifier` in api-server-grouping.ts).\n * Routes are collapsed to one entry per API surface.\n *\n * Discovery is best-effort: a malformed template that would hard-error\n * `start-api` is downgraded to a warning here so `list` still surfaces\n * every other target.\n */\nfunction listApiSurfaces(stacks: readonly StackInfo[]): TargetEntry[] {\n const byKey = new Map<string, TargetEntry>();\n const add = (\n stackName: string,\n logicalId: string,\n cdkPath: string | undefined,\n kind: string\n ): void => {\n const key = `${stackName}:${logicalId}`;\n if (!byKey.has(key)) byKey.set(key, makeEntry(stackName, logicalId, cdkPath, kind));\n };\n\n try {\n for (const route of discoverRoutes(stacks)) {\n if (!route.apiStackName) continue;\n // Mirror start-api's own identifier rule: a Function URL is keyed by\n // its BACKING LAMBDA's logical ID, every other surface by the API\n // resource's. Using the URL resource's own logical ID would print a\n // `Stack:LogicalId` that `start-api [target]` rejects.\n const logicalId =\n route.source === 'function-url' ? route.lambdaLogicalId : route.apiLogicalId;\n if (!logicalId) continue;\n add(route.apiStackName, logicalId, route.apiCdkPath, apiKindLabel(route.source));\n }\n } catch (err) {\n getLogger().warn(\n `Could not enumerate REST / HTTP / Function URL targets: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n\n const { apis, errors } = discoverWebSocketApis(stacks);\n for (const api of apis) {\n add(api.apiStackName, api.apiLogicalId, api.apiCdkPath, 'WebSocket');\n }\n for (const e of errors) {\n getLogger().warn(`Could not enumerate a WebSocket API target: ${e}`);\n }\n\n return [...byKey.values()];\n}\n\n/**\n * Enumerate the application Load Balancers `cdkl start-alb` can front. Only\n * `Type: application` (the default) is included — NLBs / Gateway LBs are a\n * different architecture the local front-door does not emulate.\n */\nfunction scanApplicationLoadBalancers(stacks: readonly StackInfo[]): TargetEntry[] {\n const entries: TargetEntry[] = [];\n for (const stack of stacks) {\n const resources = stack.template.Resources ?? {};\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ElasticLoadBalancingV2::LoadBalancer') continue;\n const type = (resource.Properties as Record<string, unknown> | undefined)?.['Type'];\n if (type !== undefined && type !== 'application') continue;\n entries.push(makeEntry(stack.stackName, logicalId, readCdkPathOrUndefined(resource)));\n }\n }\n return entries;\n}\n\n/**\n * Walk every synthesized stack and collect the resources the `cdkl` commands\n * can run locally, grouped by command.\n *\n * Pure over the synthesized {@link StackInfo}[] — no Docker / AWS /\n * filesystem access — so both `cdkl list` and the interactive target\n * pickers can share it.\n */\nexport function listTargets(stacks: readonly StackInfo[]): TargetListing {\n return {\n lambdas: sortEntries(scanByType(stacks, 'AWS::Lambda::Function')),\n apis: sortApiEntries(listApiSurfaces(stacks)),\n ecsServices: sortEntries(scanByType(stacks, 'AWS::ECS::Service')),\n ecsTaskDefinitions: sortEntries(scanByType(stacks, 'AWS::ECS::TaskDefinition')),\n agentCoreRuntimes: sortEntries(scanByType(stacks, AGENTCORE_RUNTIME_TYPE)),\n loadBalancers: sortEntries(scanApplicationLoadBalancers(stacks)),\n };\n}\n\nconst pathOf = (e: TargetEntry): string => e.displayPath ?? e.qualifiedId;\n\n/** Stable, human-readable ordering: by display path, falling back to the qualified ID. */\nfunction sortEntries(entries: TargetEntry[]): TargetEntry[] {\n return [...entries].sort((a, b) => pathOf(a).localeCompare(pathOf(b)));\n}\n\n/** Display order for API surface kinds; entries are grouped in this order. */\nconst API_KIND_ORDER = ['HTTP API v2', 'REST API v1', 'Function URL', 'WebSocket'];\n\n/**\n * Order API surfaces by stack, then kind (in {@link API_KIND_ORDER}), then\n * display path — so a multi-stack app shows each stack's APIs as a block,\n * grouped by kind inside it (single-stack apps are simply kind-grouped). Used\n * by both `cdkl list` and the interactive picker. Exported for unit testing.\n */\nexport function sortApiEntries(entries: TargetEntry[]): TargetEntry[] {\n return [...entries].sort((a, b) => {\n if (a.stackName !== b.stackName) return a.stackName.localeCompare(b.stackName);\n const ka = API_KIND_ORDER.indexOf(a.kind ?? '');\n const kb = API_KIND_ORDER.indexOf(b.kind ?? '');\n if (ka !== kb) return ka - kb;\n return pathOf(a).localeCompare(pathOf(b));\n });\n}\n\n/** Total number of targets across every category. */\nexport function countTargets(listing: TargetListing): number {\n return (\n listing.lambdas.length +\n listing.apis.length +\n listing.ecsServices.length +\n listing.ecsTaskDefinitions.length +\n listing.agentCoreRuntimes.length +\n listing.loadBalancers.length\n );\n}\n","import { MultiSelectPrompt } from '@clack/core';\nimport {\n confirm,\n isCancel,\n select,\n S_BAR,\n S_BAR_END,\n S_BAR_START,\n S_CHECKBOX_ACTIVE,\n S_CHECKBOX_INACTIVE,\n S_CHECKBOX_SELECTED,\n} from '@clack/prompts';\nimport {\n CdkLocalError,\n InteractiveTtyRequiredError,\n TargetSelectionCancelledError,\n} from '../utils/error-handler.js';\nimport { getEmbedConfig } from './embed-config.js';\nimport type { TargetEntry } from './target-lister.js';\n\n// Minimal raw-ANSI helpers (cdk-local has no color-lib dependency; the\n// logger uses raw escapes too). Kept local to the picker render.\nconst ANSI = {\n cyan: (s: string): string => `\\x1b[36m${s}\\x1b[0m`,\n green: (s: string): string => `\\x1b[32m${s}\\x1b[0m`,\n};\n\ntype PickerOption = { value: string; label: string; hint?: string };\n\n/**\n * True when both stdin and stdout are TTYs — the precondition for any\n * interactive prompt. In a pipe / CI / non-interactive shell this is\n * false and the caller must fall back (error, or the command's default\n * behavior) rather than block on a prompt that can never be answered.\n */\nexport function isInteractive(): boolean {\n return Boolean(process.stdin.isTTY && process.stdout.isTTY);\n}\n\nfunction toOption(entry: TargetEntry): { value: string; label: string; hint?: string } {\n // The display path is the recommended copy-paste form; fall back to the\n // stack-qualified logical ID when the resource has no `aws:cdk:path`.\n const value = entry.displayPath ?? entry.qualifiedId;\n const option: { value: string; label: string; hint?: string } = { value, label: value };\n // Surface the API surface kind (REST API v1 / HTTP API v2 / Function URL /\n // WebSocket) as the hint so otherwise-similar start-api targets are\n // distinguishable. The stack-qualified logical ID is intentionally NOT\n // shown — this is a CDK tool, so the display path is the natural\n // identifier; `cdkl list -l` still prints the logical ID when needed.\n if (entry.kind) option.hint = entry.kind;\n return option;\n}\n\n/**\n * Prompt for exactly one target. Caller must have already confirmed a\n * TTY ({@link isInteractive}) and a non-empty `entries`. Throws\n * {@link TargetSelectionCancelledError} on Ctrl+C / Esc.\n */\nexport async function pickOneTarget(message: string, entries: TargetEntry[]): Promise<string> {\n const chosen = await select({\n message: `${message} (up/down to move, enter to select)`,\n options: entries.map(toOption),\n });\n if (isCancel(chosen)) throw new TargetSelectionCancelledError();\n return chosen as string;\n}\n\n/**\n * The selected-value array after a bulk action — `all` selects every\n * option, `none` clears the selection. Pure (no prompt state) so the\n * arrow-key bulk-select wiring is unit-testable without a TTY.\n */\nexport function bulkSelectValues(options: PickerOption[], action: 'all' | 'none'): string[] {\n return action === 'all' ? options.map((o) => o.value) : [];\n}\n\n/** Pre-compute the aligned `[kind] ` tag per option (blank when no kind). */\nfunction kindTags(opts: PickerOption[]): string[] {\n const raw = opts.map((o) => (o.hint ? `[${o.hint}] ` : ''));\n const width = Math.max(0, ...raw.map((t) => t.length));\n return raw.map((t) => t.padEnd(width));\n}\n\n/**\n * Prompt for one or more targets. Caller must have already confirmed a TTY\n * and a non-empty `entries`. Throws {@link TargetSelectionCancelledError}\n * on Ctrl+C / Esc, on an empty selection (the user chose nothing), or when\n * the confirmation step is declined.\n *\n * Built on `@clack/core`'s `MultiSelectPrompt` (rather than the high-level\n * `multiselect`) so it can add bulk-selection keys the high-level wrapper\n * does not expose: Up/Down move, Space toggles, **Right selects all**,\n * **Left clears all**, Enter confirms. `MultiSelectPrompt` tracks the\n * selection in `this.value`, so the Right/Left handlers set it directly —\n * the same mechanism the built-in `toggleAll` uses.\n *\n * Rows start UNSELECTED. Each row is prefixed with its surface kind\n * (`[HTTP API v2] MyApi`) — always shown, padded so labels align. Submitting\n * with nothing selected exits cleanly; a non-empty selection goes through a\n * Y/n confirmation before returning.\n */\nexport async function pickManyTargets(message: string, entries: TargetEntry[]): Promise<string[]> {\n const opts: PickerOption[] = entries.map(toOption);\n const tags = kindTags(opts);\n // `[kind] label` per value, for the confirmation bullet list.\n const displayByValue = new Map(\n opts.map((o) => [o.value, o.hint ? `[${o.hint}] ${o.label}` : o.label])\n );\n\n // Loop so that declining the confirmation returns to the picker with the\n // current selection preserved (via `initialValues`).\n let initialValues: string[] = [];\n for (;;) {\n const prompt = new MultiSelectPrompt<PickerOption>({\n options: opts,\n // Allow an empty submit — handled below (confirm \"exit?\"), rather than\n // clack's built-in \"select at least one\" error.\n required: false,\n ...(initialValues.length > 0 ? { initialValues } : {}),\n render() {\n if (this.state === 'submit' || this.state === 'cancel') {\n return `${S_BAR_START} ${message}\\n${S_BAR} ${(this.value ?? []).length} selected`;\n }\n // Key hints live on the message line, `action: key` form, `|`-separated.\n const header = `${S_BAR_START} ${message} (toggle: space | all: → | none: ← | confirm: enter)`;\n const selected = new Set(this.value ?? []);\n const rows = this.options.map((opt, i) => {\n const isActive = i === this.cursor;\n const isSelected = selected.has(opt.value);\n // Checked box is green (matching the confirm prompt's check colour);\n // the cursor row's box is cyan; otherwise the default checkbox.\n const box = isSelected\n ? ANSI.green(S_CHECKBOX_SELECTED)\n : isActive\n ? ANSI.cyan(S_CHECKBOX_ACTIVE)\n : S_CHECKBOX_INACTIVE;\n // Row text: active = cyan, selected = green, otherwise the default\n // fg (white) — never dim/grey, which is hard to read. The `[kind]`\n // tag follows the row's colour.\n const text = `${tags[i]}${opt.label}`;\n const coloured = isActive ? ANSI.cyan(text) : isSelected ? ANSI.green(text) : text;\n return `${S_BAR} ${box} ${coloured}`;\n });\n return `${header}\\n${rows.join('\\n')}\\n${S_BAR_END}`;\n },\n });\n\n // Right selects every row; Left clears. Setting `prompt.value` is exactly\n // how the built-in `toggleAll` works; the prompt re-renders after each\n // keypress. Note: MultiSelectPrompt ALSO maps Left/Right onto its cursor\n // (as Up/Down) and that fires before this handler, so after a bulk change\n // we pin the cursor to the top for a stable, predictable position.\n prompt.on('key', (_char, info) => {\n if (info?.name !== 'right' && info?.name !== 'left') return;\n prompt.value = bulkSelectValues(opts, info.name === 'right' ? 'all' : 'none');\n prompt.cursor = 0;\n });\n\n const picked = await prompt.prompt();\n if (isCancel(picked)) throw new TargetSelectionCancelledError();\n const values = (picked as string[] | undefined) ?? [];\n\n if (values.length === 0) {\n const exit = await confirm({ message: 'Nothing selected. Exit without running anything?' });\n if (isCancel(exit)) throw new TargetSelectionCancelledError();\n if (exit === true) throw new TargetSelectionCancelledError();\n initialValues = [];\n continue; // back to the picker\n }\n\n const bullets = values.map((v) => ` • ${displayByValue.get(v) ?? v}`).join('\\n');\n const noun = values.length === 1 ? 'this target' : `these ${values.length} targets`;\n const ok = await confirm({ message: `Run ${noun}?\\n${bullets}` });\n if (isCancel(ok)) throw new TargetSelectionCancelledError();\n if (ok === true) return values;\n initialValues = values; // declined -> back to the picker, selection kept\n }\n}\n\ninterface ResolveParams {\n /** Candidate targets for this command's category. */\n entries: TargetEntry[];\n /** Prompt header, e.g. \"Select a Lambda function to invoke\". */\n message: string;\n /** Plural noun for the empty-candidates error, e.g. \"Lambda functions\". */\n noun: string;\n /** The command's existing \"target argument is required\" error, thrown when omitted in a non-TTY context. */\n onMissing: () => CdkLocalError;\n}\n\nfunction ensureCanPrompt(onMissing: () => CdkLocalError): void {\n if (isInteractive()) return;\n // Target omitted in a non-interactive context — preserve the command's\n // original \"required argument\" behavior.\n throw onMissing();\n}\n\nfunction ensureHasCandidates(count: number, noun: string): void {\n if (count > 0) return;\n throw new InteractiveTtyRequiredError(\n `No ${noun} found in this CDK app to choose from. Run \\`${getEmbedConfig().cliName} list\\` to see what is available.`\n );\n}\n\n/**\n * Resolve a single positional target, prompting interactively when the\n * target is omitted in a TTY.\n *\n * - `provided` set → returned as-is (no prompt).\n * - omitted, TTY → prompt.\n * - omitted, no TTY → `onMissing()` (the command's required-arg error).\n */\nexport async function resolveSingleTarget(\n provided: string | undefined,\n params: ResolveParams\n): Promise<string> {\n if (provided) return provided;\n ensureCanPrompt(params.onMissing);\n ensureHasCandidates(params.entries.length, params.noun);\n return pickOneTarget(params.message, params.entries);\n}\n\n/**\n * Resolve one or more positional targets (the `start-service` variadic\n * shape), prompting with a multi-select when appropriate. Same trigger\n * rules as {@link resolveSingleTarget}.\n */\nexport async function resolveMultiTarget(\n provided: string[],\n params: ResolveParams\n): Promise<string[]> {\n if (provided.length > 0) return provided;\n ensureCanPrompt(params.onMissing);\n ensureHasCandidates(params.entries.length, params.noun);\n return pickManyTargets(params.message, params.entries);\n}\n","import { NonInteractiveIoHost, type IoMessage } from '@aws-cdk/toolkit-lib';\n\n/**\n * Custom IoHost that prevents cdk-local from coloring CDK app subprocess\n * stderr lines as errors.\n *\n * `@aws-cdk/toolkit-lib` classifies every line a synthesized CDK app\n * emits on stderr as `CDK_ASSEMBLY_E1002` — an error-level message —\n * which the default `NonInteractiveIoHost` colors red via chalk. CDK\n * apps routinely emit non-error progress on stderr (e.g. the\n * \"Bundling asset ...\" lines `aws-cdk-lib`'s asset bundlers print at\n * synth time), so the default styling fires red on benign output and\n * trains users to dismiss the color in the UI.\n *\n * cdk-local re-classifies `CDK_ASSEMBLY_E1002` as info-level so the\n * line renders in the default terminal color. Real CDK app failures\n * still surface — toolkit-lib raises an `AssemblyError` when the\n * subprocess exits non-zero and the parent throws straight out of\n * `Synthesizer.synthesize()`. The only change here is the color of the\n * progress lines mid-synth.\n *\n * It also re-classifies the synth-success notifications\n * (`CDK_TOOLKIT_I1901` single-stack / `CDK_TOOLKIT_I1902` multi-stack,\n * \"Successfully synthesized to ...\") from `result` level to `info`.\n * toolkit-lib sends `result`-level messages to stdout unconditionally,\n * which pollutes the stdout of every synthesizing command. Re-leveling\n * to `info` routes them to stderr in a normal terminal. This matters\n * most for `cdkl list`, whose stdout is a parseable target list — that\n * command additionally writes its own `Synthesizing...` status to\n * stderr so its stdout is exactly the list. These are status lines no\n * caller parses from cdk-local's stdout.\n */\nexport class CdklIoHost extends NonInteractiveIoHost {\n async notify(msg: IoMessage<unknown>): Promise<void> {\n if (\n msg.code === 'CDK_ASSEMBLY_E1002' ||\n msg.code === 'CDK_TOOLKIT_I1901' ||\n msg.code === 'CDK_TOOLKIT_I1902'\n ) {\n return super.notify({ ...msg, level: 'info' });\n }\n return super.notify(msg);\n }\n}\n","import {\n AssetManifestArtifact,\n type CloudFormationStackArtifact,\n} from '@aws-cdk/cloud-assembly-api';\nimport { Toolkit, CdkAppMultiContext, BaseCredentials } from '@aws-cdk/toolkit-lib';\nimport { CdklIoHost } from './cdkl-io-host.js';\nimport type { CloudFormationTemplate } from '../types/resource.js';\n\n/**\n * Stack information extracted from a CDK Cloud Assembly.\n *\n * cdk-local delegates synthesis to `@aws-cdk/toolkit-lib`\n * (`Toolkit.fromCdkApp()` + `Toolkit.synth()`) and maps each synthesized\n * `CloudFormationStackArtifact` to a `StackInfo` record.\n */\nexport interface StackInfo {\n /** Physical CloudFormation stack name (e.g., \"MyStage-MyStack\"). */\n stackName: string;\n\n /**\n * Hierarchical display name from CDK synth (e.g., \"MyStage/MyStack\"\n * for stacks under a Stage, or \"MyStack\" at the top level). Falls\n * back to `artifactId` when the artifact does not carry one.\n */\n displayName: string;\n\n /** Artifact ID in manifest (typically equals `stackName` for top-level stacks). */\n artifactId: string;\n\n /** Synthesized CloudFormation template (JSON). */\n template: CloudFormationTemplate;\n\n /** Asset manifest file path (absolute). Populated when the stack ships assets. */\n assetManifestPath?: string | undefined;\n\n /** Stack dependency names (other stack artifact IDs this stack depends on). */\n dependencyNames: string[];\n\n /** Target region from CDK environment (`Stack.region`). */\n region?: string | undefined;\n\n /** Target account from CDK environment (`Stack.account`). */\n account?: string | undefined;\n\n /**\n * Stack-level termination protection (CDK `Stack.terminationProtection`).\n * Informational only for cdk-local — local execution does not honor\n * deploy-time termination protection.\n */\n terminationProtection?: boolean | undefined;\n\n /**\n * Per-logical-id absolute file paths of nested templates one level\n * below this stack — populated when the parent template contains\n * `AWS::CloudFormation::Stack` resources whose\n * `Metadata['aws:asset:path']` points at the child's\n * `<file>.nested.template.json` sibling in the same `cdk.out`\n * directory.\n *\n * TODO(phase-2d-2): populate from the synthesized assembly. Until the\n * 4 CLI factories actually consume nested templates, leaving this\n * `undefined` is non-fatal for cdk-local's smoke path.\n */\n nestedTemplates?: Record<string, string> | undefined;\n}\n\n/**\n * Reads and parses a CDK Cloud Assembly via `@aws-cdk/toolkit-lib`.\n *\n * Two accepted inputs:\n *\n * - `read(cdkAppCommand)` — pass a CDK app entrypoint command (the\n * same string `cdk.json`'s `app` field carries, e.g.\n * `\"npx tsx bin/app.ts\"`). Internally invokes `Toolkit.fromCdkApp()`,\n * which forks the CDK app as a subprocess with the standard\n * `CDK_OUTDIR` / `CDK_CONTEXT_JSON` / `CDK_DEFAULT_*` env contract,\n * waits for the assembly to materialize, then synths it.\n *\n * - `readFromDirectory(assemblyDir)` — point at an existing `cdk.out`\n * directory produced by a prior `cdk synth`. Skips the subprocess\n * hop; useful in CI where the synth step has already run.\n *\n * Both paths produce the same `StackInfo[]` shape so downstream\n * consumers (Lambda resolver, route discovery, ECS task resolver, etc.)\n * do not branch on the input mode.\n */\n/**\n * Read options forwarded to `Toolkit.fromCdkApp` / `Toolkit.fromAssemblyDirectory`.\n */\nexport interface AssemblyReadOptions {\n /** Output directory for synthesized assembly (default: `cdk.out`). */\n outdir?: string;\n /** Additional env vars passed to the CDK app subprocess (e.g. `AWS_PROFILE`, `AWS_REGION`). */\n env?: Record<string, string | undefined>;\n /**\n * AWS profile used for toolkit-lib's OWN AWS calls — most importantly\n * the synth-time context lookups (assume `cdk-hnb659fds-lookup-role-*`\n * + the SSM / VPC / etc. provider call). Without this, those lookups\n * fall back to the parent process's default credential chain even\n * when `--profile` was supplied, so a cross-account lookup-role\n * assume resolves with the wrong account and synthesis aborts with\n * `AssemblyError: Found errors`. Setting `env.AWS_PROFILE` alone is\n * not enough: that only reaches the forked CDK app subprocess, not\n * the lookup machinery that runs in this process.\n */\n profile?: string;\n /** Default region for toolkit-lib's own AWS calls (context lookups). */\n region?: string;\n /**\n * Working directory the CDK app subprocess is executed in. Defaults\n * to the current process cwd. When `context` is also set, this is\n * also the directory `CdkAppMultiContext` resolves `cdk.json` /\n * `cdk.context.json` / `~/.cdk.json` against.\n */\n workingDirectory?: string;\n /**\n * Commandline context overrides (CDK CLI `-c key=value`).\n *\n * Threaded through `CdkAppMultiContext(workingDirectory, context)` so\n * cdk.json / cdk.context.json / ~/.cdk.json remain the base layer and\n * CLI overrides win for keys they touch. Empty / undefined leaves\n * toolkit-lib's default context store (CdkAppMultiContext with no\n * commandline context) in place.\n */\n context?: Record<string, string>;\n}\n\nexport class AssemblyReader {\n /**\n * Synthesize the CDK app and return `StackInfo` for every stack\n * artifact in the resulting Cloud Assembly.\n */\n async read(cdkAppCommand: string, options: AssemblyReadOptions = {}): Promise<StackInfo[]> {\n const toolkit = new Toolkit({\n ioHost: new CdklIoHost(),\n sdkConfig: {\n baseCredentials: BaseCredentials.awsCliCompatible({\n ...(options.profile !== undefined && { profile: options.profile }),\n ...(options.region !== undefined && { defaultRegion: options.region }),\n }),\n },\n });\n const hasContextOverrides =\n options.context !== undefined && Object.keys(options.context).length > 0;\n const source = await toolkit.fromCdkApp(cdkAppCommand, {\n ...(options.outdir !== undefined && { outdir: options.outdir }),\n ...(options.env !== undefined && { env: options.env }),\n ...(options.workingDirectory !== undefined && {\n workingDirectory: options.workingDirectory,\n }),\n ...(hasContextOverrides && {\n contextStore: new CdkAppMultiContext(\n options.workingDirectory ?? process.cwd(),\n options.context\n ),\n }),\n });\n const cached = await toolkit.synth(source);\n try {\n return cached.cloudAssembly.stacks.map((stack) => mapStackArtifact(stack));\n } finally {\n await cached.dispose();\n }\n }\n\n /**\n * Read a pre-synthesized Cloud Assembly directory (no subprocess).\n */\n async readFromDirectory(assemblyDir: string): Promise<StackInfo[]> {\n const toolkit = new Toolkit({ ioHost: new CdklIoHost() });\n const source = await toolkit.fromAssemblyDirectory(assemblyDir);\n const cached = await toolkit.synth(source);\n try {\n return cached.cloudAssembly.stacks.map((stack) => mapStackArtifact(stack));\n } finally {\n await cached.dispose();\n }\n }\n}\n\nfunction mapStackArtifact(stack: CloudFormationStackArtifact): StackInfo {\n const info: StackInfo = {\n stackName: stack.stackName,\n displayName: stack.displayName ?? stack.id,\n artifactId: stack.id,\n template: stack.template as CloudFormationTemplate,\n dependencyNames: stack.dependencies.map((d) => d.id),\n };\n if (stack.environment.region) {\n info.region = stack.environment.region;\n }\n if (stack.environment.account) {\n info.account = stack.environment.account;\n }\n if (stack.terminationProtection !== undefined) {\n info.terminationProtection = stack.terminationProtection;\n }\n // Locate the AssetManifestArtifact among the stack's dependencies and\n // surface its absolute path. Downstream resolvers (`lambda-resolver.ts`,\n // `ecs-task-resolver.ts`, etc.) read `dirname(assetManifestPath)` to find\n // the cdk.out directory where every `asset.<hash>` subdirectory lives —\n // without this, the fallback `process.cwd()` resolves asset directories\n // against the user's CWD instead of cdk.out, so `cdkl invoke`'s\n // asset-directory existence check fires a `LocalInvokeResolutionError`\n // for every Lambda whose code was synthesized as a separate asset.\n const assetManifest = stack.dependencies.find(\n (d): d is AssetManifestArtifact => d instanceof AssetManifestArtifact\n );\n if (assetManifest) {\n info.assetManifestPath = assetManifest.file;\n }\n return info;\n}\n","import { existsSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { AssemblyReader, type StackInfo, type AssemblyReadOptions } from './assembly-reader.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Synthesis options accepted by the four `cdkl` commands.\n *\n * cdk-local delegates the heavy lifting to `@aws-cdk/toolkit-lib`'s\n * `Toolkit.fromCdkApp()` (via {@link AssemblyReader}), which handles\n * subprocess execution + manifest parsing + context resolution\n * internally — there is no per-field substitution layer.\n */\nexport interface SynthesisOptions {\n /** CDK app command or pre-synthesized assembly directory (cdk.json `app`). */\n app: string;\n /** Output directory for synthesis (default: `cdk.out`). */\n output?: string;\n /** AWS profile passed to the CDK app subprocess via `AWS_PROFILE`. */\n profile?: string;\n /** AWS region passed to the CDK app subprocess via `AWS_REGION` / `CDK_DEFAULT_REGION`. */\n region?: string;\n /**\n * Context key-value pairs (CLI `-c`/`--context`).\n *\n * Threaded through `CdkAppMultiContext(workingDirectory, context)` so\n * `cdk.json` / `cdk.context.json` / `~/.cdk.json` remain the base\n * layer and CLI overrides win for keys they touch. Empty / undefined\n * leaves toolkit-lib's default context store in place.\n */\n context?: Record<string, string>;\n}\n\nexport interface SynthesisResult {\n /** All stacks in the assembly. */\n stacks: StackInfo[];\n}\n\n/**\n * Thin wrapper around {@link AssemblyReader} exposing a `Synthesizer`\n * API used by the `cdkl invoke` / `start-api` / `run-task` /\n * `start-service` commands.\n *\n * When `app` resolves to an existing directory it is read as a\n * pre-synthesized cloud assembly (no subprocess synth); otherwise it is\n * executed as the CDK app command.\n */\nexport class Synthesizer {\n async synthesize(opts: SynthesisOptions): Promise<SynthesisResult> {\n const reader = new AssemblyReader();\n\n // CDK CLI compatibility: when `--app` points at an existing directory,\n // treat it as a pre-synthesized cloud assembly and skip the subprocess\n // synth — `Toolkit.fromCdkApp()` would otherwise try to exec the\n // directory as a shell command and fail with \"is a directory\".\n // (Mirrors aws-cdk's `exec.ts`: \"bypass 'synth' if app points to a\n // cloud assembly\".) Context / profile / region overrides do not apply\n // to an already-synthesized assembly, so they are intentionally ignored\n // on this path.\n const appPath = resolve(opts.app);\n if (existsSync(appPath) && statSync(appPath).isDirectory()) {\n getLogger().debug(`Using pre-synthesized cloud assembly at ${appPath}`);\n const stacks = await reader.readFromDirectory(appPath);\n return { stacks };\n }\n\n const readOpts: AssemblyReadOptions = {};\n if (opts.output !== undefined) {\n readOpts.outdir = opts.output;\n }\n const env: Record<string, string | undefined> = {};\n if (opts.profile !== undefined) {\n env['AWS_PROFILE'] = opts.profile;\n // Also threaded into the Toolkit's own SDK config (not just the\n // subprocess env) so synth-time context lookups assume the\n // lookup-role with this profile rather than the parent process's\n // default credential chain.\n readOpts.profile = opts.profile;\n }\n if (opts.region !== undefined) {\n env['AWS_REGION'] = opts.region;\n env['CDK_DEFAULT_REGION'] = opts.region;\n readOpts.region = opts.region;\n }\n if (Object.keys(env).length > 0) {\n readOpts.env = env;\n }\n if (opts.context !== undefined && Object.keys(opts.context).length > 0) {\n readOpts.context = opts.context;\n }\n const stacks = await reader.read(opts.app, readOpts);\n return { stacks };\n }\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { getLogger } from '../utils/logger.js';\nimport { getEmbedConfig } from '../local/embed-config.js';\n\ninterface CdkJson {\n app?: string;\n watch?: { include?: string | string[]; exclude?: string | string[] };\n}\n\nfunction loadCdkJson(): CdkJson | null {\n const filePath = resolve(process.cwd(), 'cdk.json');\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as CdkJson;\n } catch (error) {\n getLogger().warn(\n `Failed to parse ${filePath}: ${error instanceof Error ? error.message : String(error)}`\n );\n return null;\n }\n}\n\nfunction normalizeGlobList(value: string | string[] | undefined): string[] {\n const arr = typeof value === 'string' ? [value] : Array.isArray(value) ? value : [];\n return arr.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0);\n}\n\n/**\n * Resolve the `--app` option from CLI, `CDKL_APP` env var, or `cdk.json`.\n *\n * Priority: CLI option > `CDKL_APP` env > `cdk.json` `app` field > undefined.\n *\n * `@aws-cdk/toolkit-lib`'s `Toolkit.fromCdkApp(...)` requires the CDK app\n * command as a positional argument and does NOT auto-resolve `cdk.json`'s\n * `app` field, so cdk-local resolves it here before handing the command to\n * the toolkit. `~/.cdk.json` context is intentionally NOT read here —\n * `CdkAppMultiContext` inside `assembly-reader.ts` already merges it.\n */\nexport function resolveApp(cliApp?: string): string | undefined {\n if (cliApp) return cliApp;\n\n const envApp = process.env[`${getEmbedConfig().envPrefix}_APP`];\n if (envApp) return envApp;\n\n return loadCdkJson()?.app ?? undefined;\n}\n\n/** Resolved `cdk.json` `watch` block (include / exclude glob lists). */\nexport interface CdkWatchConfig {\n /** Globs of source paths whose changes trigger a `--watch` reload. */\n include: string[];\n /** Globs of source paths that never trigger a reload. */\n exclude: string[];\n}\n\n/**\n * Resolve the `cdk.json` `watch` block, mirroring `cdk watch`'s\n * include / exclude semantics for `cdkl start-api --watch` source-tree\n * watching.\n *\n * Defaults when the keys are absent: `include` -> `['**']` (watch the\n * whole app directory), `exclude` -> `[]`. Unlike `cdk watch`, a missing\n * `watch` block is NOT an error — `--watch` still works against the\n * defaults. The caller layers in mandatory excludes (the synth output\n * directory, `node_modules`, `.git`) so re-synth writes never re-trigger\n * a reload and large noise directories are pruned.\n */\nexport function resolveWatchConfig(): CdkWatchConfig {\n const watch = loadCdkJson()?.watch;\n const include = normalizeGlobList(watch?.include);\n return {\n // An empty / all-invalid `include` (e.g. `[]` or `\"\"`) would make the\n // watcher match nothing and silently never reload — fall back to `**`.\n include: include.length > 0 ? include : ['**'],\n exclude: normalizeGlobList(watch?.exclude),\n };\n}\n","/**\n * Resolve CloudFormation template `Parameters` of the SSM-backed types\n * `AWS::SSM::Parameter::Value<String>` / `AWS::SSM::Parameter::Value<List<String>>`\n * (what CDK synthesizes for `ssm.StringParameter.valueForStringParameter(...)`)\n * into their deployed values via SSM Parameter Store.\n *\n * Motivation (issue #94): a container / Lambda env var that `Ref`s such a\n * parameter cannot be resolved from the `--from-cfn-stack` state source.\n * That source is built from `ListStackResources` (deployed RESOURCES); a\n * CloudFormation PARAMETER is not a resource, so the `Ref` misses\n * `context.resources[<id>]` in `state-resolver.ts` and the env var is\n * warn-and-dropped. The synthesized template, however, carries the SSM\n * parameter NAME in each entry's `Default`:\n *\n * \"Parameters\": {\n * \"SsmParameterValue...Parameter\": {\n * \"Type\": \"AWS::SSM::Parameter::Value<String>\",\n * \"Default\": \"/path/to/the/parameter\"\n * }\n * }\n *\n * and the run already has working AWS credentials / region (the same ones\n * `--from-cfn-stack` uses for `ListStackResources`). This module reads each\n * parameter NAME from `Default`, batch-resolves the values via SSM\n * `GetParameters`, and returns a `logicalId -> value` map the CLI feeds\n * into the substitution context so a `Ref` to the parameter's logical id\n * resolves to the value instead of being dropped.\n *\n * Best-effort by design: on any SSM failure (no credentials, access\n * denied, throttling) the helper logs a warn and returns whatever it\n * could resolve (possibly nothing) — the caller then falls back to the\n * existing warn-and-drop behavior on the affected `Ref`s. It NEVER throws\n * out of the substitution pass.\n */\n\nimport { SSMClient, GetParametersCommand } from '@aws-sdk/client-ssm';\nimport { getLogger } from '../utils/logger.js';\nimport type { CloudFormationTemplate } from '../types/resource.js';\n\n/** SSM-backed CFn parameter types CDK synthesizes for SSM lookups. */\nconst SSM_STRING_TYPE = 'AWS::SSM::Parameter::Value<String>';\nconst SSM_LIST_TYPE = 'AWS::SSM::Parameter::Value<List<String>>';\n\n/**\n * A template `Parameters` entry that points at an SSM parameter and so\n * can be resolved from Parameter Store.\n */\nexport interface SsmParameterRef {\n /** Logical ID of the CFn parameter (the `Ref` target). */\n logicalId: string;\n /** SSM parameter name, read from the entry's `Default`. */\n ssmName: string;\n /** `true` for the `List<String>` variant (the value is comma-joined). */\n isList: boolean;\n}\n\n/**\n * Result of {@link resolveSsmParameters}: the resolved `logicalId -> value`\n * map plus the subset of logical IDs whose SSM parameter is a\n * `SecureString`. The decrypted SecureString values are routed through\n * docker's value-from-process-env form (`-e KEY`) downstream so they never\n * land on the `docker run` argv (issue #99); the caller threads\n * `secureStringLogicalIds` into the substitution context as\n * `sensitiveParameters` so the env-flag builder can pick the safe form.\n */\nexport interface ResolvedSsmParameters {\n /** `parameterLogicalId -> resolved value`. */\n values: Record<string, string>;\n /** Logical IDs whose SSM `Type` is `SecureString` (decrypted values). */\n secureStringLogicalIds: string[];\n}\n\n/**\n * Scan a synthesized template's `Parameters` block for entries whose\n * `Type` is one of the SSM-backed parameter types AND whose `Default`\n * carries a usable SSM parameter name. Pure — no AWS calls.\n *\n * Entries without a non-empty string `Default` are skipped (CDK always\n * synthesizes the parameter name into `Default` for the\n * `valueForStringParameter` shape; a parameter declared without a default\n * has no name we can resolve, so it stays warn-and-drop).\n *\n * Exported for unit testing.\n */\nexport function collectSsmParameterRefs(\n template: Pick<CloudFormationTemplate, 'Parameters'> | undefined\n): SsmParameterRef[] {\n const params = template?.Parameters;\n if (!params) return [];\n const out: SsmParameterRef[] = [];\n for (const [logicalId, entry] of Object.entries(params)) {\n if (!entry || typeof entry !== 'object') continue;\n const type = entry.Type;\n const isList = type === SSM_LIST_TYPE;\n if (type !== SSM_STRING_TYPE && !isList) continue;\n const ssmName = entry.Default;\n if (typeof ssmName !== 'string' || ssmName.length === 0) continue;\n out.push({ logicalId, ssmName, isList });\n }\n return out;\n}\n\n/**\n * Batch-resolve a set of {@link SsmParameterRef}s via SSM `GetParameters`\n * and return a `logicalId -> resolved value` map. `List<String>`\n * parameters are comma-joined (CloudFormation surfaces the list type as a\n * comma-delimited string when the value is consumed as a `Ref`).\n *\n * `GetParameters` accepts up to 10 names per call, so the refs are\n * chunked. Names that SSM reports as invalid (`InvalidParameters`) are\n * left out of the result map so the caller falls back to warn-and-drop\n * on the corresponding `Ref`. `WithDecryption: true` is set so Secure\n * String parameters resolve too (matching how CloudFormation resolves\n * the `AWS::SSM::Parameter::Value<String>` type at deploy time).\n *\n * Security note (issue #99): a decrypted SecureString value resolved here\n * would otherwise be baked into the container's `Environment` like any\n * other resolved value and could appear on the `docker run -e KEY=VALUE`\n * argv (visible in host `ps`). To prevent that, the returned\n * {@link ResolvedSsmParameters.secureStringLogicalIds} flags every\n * SecureString parameter; downstream the consuming env keys are routed\n * through docker's value-from-process-env form (`-e KEY`, value supplied\n * via the spawned docker process's env) so the secret never lands on the\n * argv. The value still appears in `docker inspect` Config.Env, which is\n * inherent to container env and matches deployed behavior.\n *\n * Best-effort: a failed `GetParameters` chunk logs a warn and is skipped\n * (the other chunks still contribute their values); the function never\n * throws.\n *\n * `label` is the state-source label (e.g. `--from-cfn-stack`) used to\n * prefix warns so the user can tell which source produced them.\n *\n * Exported for unit testing.\n */\nexport async function resolveSsmParameters(\n client: SSMClient,\n refs: readonly SsmParameterRef[],\n label: string\n): Promise<ResolvedSsmParameters> {\n const out: ResolvedSsmParameters = { values: {}, secureStringLogicalIds: [] };\n if (refs.length === 0) return out;\n\n const logger = getLogger();\n\n // GetParameters accepts at most 10 names per call.\n const CHUNK = 10;\n // Map SSM name -> the refs that requested it (multiple logical IDs may\n // point at the same SSM name).\n const byName = new Map<string, SsmParameterRef[]>();\n for (const ref of refs) {\n const list = byName.get(ref.ssmName);\n if (list) list.push(ref);\n else byName.set(ref.ssmName, [ref]);\n }\n const uniqueNames = [...byName.keys()];\n\n for (let i = 0; i < uniqueNames.length; i += CHUNK) {\n const names = uniqueNames.slice(i, i + CHUNK);\n let resolved: Array<{\n Name?: string | undefined;\n Value?: string | undefined;\n Type?: string | undefined;\n }>;\n try {\n const resp = await client.send(\n new GetParametersCommand({ Names: names, WithDecryption: true })\n );\n resolved = resp.Parameters ?? [];\n const invalid = resp.InvalidParameters ?? [];\n if (invalid.length > 0) {\n logger.warn(\n `${label}: SSM GetParameters reported invalid parameter name(s): ${invalid.join(', ')}. ` +\n `Ref to the matching CloudFormation parameter(s) will warn-and-drop (was the SSM parameter created?).`\n );\n }\n } catch (err) {\n logger.warn(\n `${label}: SSM GetParameters(${names.join(', ')}) failed: ${formatSsmError(err)}. ` +\n `Ref to the matching CloudFormation parameter(s) will warn-and-drop (grant ssm:GetParameters or override via --env-vars).`\n );\n continue;\n }\n\n for (const p of resolved) {\n if (typeof p.Name !== 'string' || typeof p.Value !== 'string') continue;\n const requesters = byName.get(p.Name);\n if (!requesters) continue;\n const isSecure = p.Type === 'SecureString';\n for (const ref of requesters) {\n // CloudFormation surfaces a `List<String>` SSM value as a\n // comma-delimited string when referenced; SSM returns the value\n // already comma-joined for StringList parameters, so we pass it\n // through verbatim — the `isList` flag is retained for clarity\n // and future divergence but needs no transform here.\n out.values[ref.logicalId] = p.Value;\n // SecureString values are decrypted (WithDecryption) — flag the\n // logical ID so downstream the consuming env key is kept off the\n // `docker run` argv (issue #99).\n if (isSecure) out.secureStringLogicalIds.push(ref.logicalId);\n }\n }\n }\n\n return out;\n}\n\n/**\n * Format an SSM SDK error as `<name>: <message>` so the warn names the\n * error class (e.g. `AccessDeniedException`, `ThrottlingException`).\n * Mirrors `formatAwsErrorForWarn` in `cfn-local-state-provider.ts`.\n * Exported for unit testing.\n */\nexport function formatSsmError(err: unknown): string {\n if (!(err instanceof Error)) return String(err);\n const name = err.name && err.name !== 'Error' ? err.name : undefined;\n return name !== undefined ? `${name}: ${err.message}` : err.message;\n}\n","/**\n * `CfnLocalStateProvider` — implementation of {@link LocalStateProvider}\n * backed by a deployed CloudFormation stack. Powers `cdkl *\n * --from-cfn-stack` (issue #606).\n *\n * The shape mirrors the SAM CLI's `sam local invoke --stack-name X`\n * behavior: reach into a deployed CFn stack via `ListStackResources`\n * to look up physical IDs of every same-stack resource, then make those\n * IDs available to the existing `state-resolver.ts` substitution engine.\n * This lets `cdkl *` substitute env vars / secrets / images that\n * reference deployed resources in a CDK app deployed via the upstream\n * CDK CLI (`cdk deploy` → CloudFormation) WITHOUT first migrating the\n * stack into a separate state store.\n *\n * Wire-format mapping:\n *\n * - `Ref: <LogicalId>` → resolved via the synthetic `ResourceState`\n * map built from `ListStackResources.StackResourceSummaries[]` (one\n * entry per `(LogicalResourceId, PhysicalResourceId, ResourceType)`\n * tuple).\n * - `Fn::GetAtt: [<LogicalId>, <Attr>]` → not resolvable from\n * `ListStackResources` (which returns physical IDs only, no\n * per-attribute values). For a Lambda function's OWN env vars this\n * gap is closed by {@link CfnLocalStateProvider.resolveDeployedFunctionEnv}:\n * because `--from-cfn-stack` means the consumer function is itself\n * deployed, CloudFormation already resolved every intrinsic (incl.\n * `Fn::GetAtt <Sibling>.Arn`) into the function's\n * `Environment.Variables` at deploy time, so the env-substitution\n * layer reads the concrete value back via\n * `lambda:GetFunctionConfiguration`. Intrinsic values that are NOT a\n * consumer-Lambda env var (e.g. an ECS container env entry) still\n * warn-and-drop and can be overridden via `--env-vars`.\n * - `Fn::ImportValue: <exportName>` → resolved via `ListExports`\n * (paginated). Same-region only — CFn exports are region-scoped.\n * - `Fn::GetStackOutput` → rejected with a clear pointer that the\n * intrinsic has no CFn equivalent (exports + outputs are the only\n * cross-stack vocabulary CFn understands).\n * - Stack outputs (consumed by both `Fn::GetStackOutput` and the\n * cross-stack-resolver's index-miss fallback) → sourced from\n * `DescribeStacks.Outputs[]`.\n *\n * Region handling: the provider takes a single region at construction\n * time (the `cdkl *` commands resolve this from\n * `--stack-region` > `--region` > `AWS_REGION` > the synth-derived\n * region per the existing `--from-state` precedence). Cross-region\n * `Fn::ImportValue` is out of scope for v1 (CFn's `ListExports` is\n * region-scoped; a future PR can add a multi-region scan if real\n * usage justifies it).\n *\n * AWS API contract notes:\n *\n * - `ListStackResources` is paginated and returns EVERY resource in\n * the stack across pages. We deliberately do NOT use\n * `DescribeStackResources` here: that API returns only the first\n * 100 resources (AWS-documented hard cap) with no `NextToken`, so a\n * stack with > 100 resources silently loses its tail — every `Ref`\n * to a dropped resource then warn-and-drops its env var. The\n * provider walks `ListStackResources`'s `NextToken` until the page\n * set is exhausted so all resources are mapped regardless of count.\n * - `DescribeStacks` is unpaginated when called with `StackName`.\n * - `ListExports` is paginated; the provider walks `NextToken` until\n * the page set is exhausted.\n */\n\nimport {\n CloudFormationClient,\n DescribeStacksCommand,\n ListExportsCommand,\n ListStackResourcesCommand,\n} from '@aws-sdk/client-cloudformation';\nimport { LambdaClient, GetFunctionConfigurationCommand } from '@aws-sdk/client-lambda';\nimport { SSMClient } from '@aws-sdk/client-ssm';\nimport { getLogger } from '../utils/logger.js';\nimport { collectSsmParameterRefs, resolveSsmParameters } from './ssm-parameter-resolver.js';\nimport type { ResolvedSsmParameters } from './ssm-parameter-resolver.js';\nimport type { CloudFormationTemplate } from '../types/resource.js';\nimport type { ResourceState } from '../types/state.js';\nimport type { CrossStackResolver } from './state-resolver.js';\nimport type { LocalStateProvider, LocalStateRecord } from './local-state-provider.js';\n\nexport interface CfnLocalStateProviderOptions {\n /**\n * CFn stack name to read physical IDs / outputs from. Required; the\n * CLI layer resolves the bare-vs-explicit form (`--from-cfn-stack`\n * with no value → uses the host stack name) before calling the\n * provider.\n */\n cfnStackName: string;\n /**\n * AWS region the CFn stack lives in. Reused from `--stack-region`\n * per issue #606 recommendation (no separate `--cfn-stack-region` flag).\n */\n region: string;\n /**\n * Optional AWS profile name. When set, threaded through to the\n * `CloudFormationClient` config so the SDK reads credentials from the\n * named profile in `~/.aws/credentials` / `~/.aws/config`. When unset,\n * the SDK's default credential chain (env vars / `AWS_PROFILE` /\n * shared config / IAM role) picks the credentials.\n *\n * Issue #628: an earlier revision captured this option but did NOT\n * pass it to the client, so `cdkl start-api --from-cfn-stack\n * <stack> --profile <profile>` silently queried the default account\n * and failed with \"Stack does not exist\" when the stack lived\n * elsewhere. Matches the threading pattern in\n * `S3LocalStateProvider` / `S3StateBackend` /\n * `src/utils/aws-region-resolver.ts` — every other cdk-local command\n * already passes `profile` straight to the SDK client.\n */\n profile?: string;\n}\n\nexport class CfnLocalStateProvider implements LocalStateProvider {\n // erasableSyntaxOnly forbids parameter-property shorthand; declare\n // fields explicitly + assign in the body.\n public readonly label = '--from-cfn-stack';\n private readonly cfnStackName: string;\n private readonly region: string;\n // The CFn client is constructed lazily on the first `load` /\n // `buildCrossStackResolver` call so a CLI invocation that never\n // exercises the provider (e.g. when the synth template has no\n // intrinsic-valued env vars) doesn't open a needless client.\n private client: CloudFormationClient | undefined;\n // The Lambda client is constructed lazily on the first\n // `resolveDeployedFunctionEnv` call so invocations that never need the\n // deployed-env fallback (no unresolvable intrinsic in any consumer\n // Lambda's env) don't open a needless client.\n private lambdaClient: LambdaClient | undefined;\n // The SSM client is constructed lazily on the first\n // `resolveTemplateSsmParameters` call so invocations whose templates\n // declare no `AWS::SSM::Parameter::Value` parameters don't open a\n // needless client.\n private ssmClient: SSMClient | undefined;\n private readonly clientOptions: { region: string; profile?: string };\n // Issue #611 NIT 2: `dispose()` is terminal. The lazy `getClient()`\n // path would otherwise resurrect the client on a post-dispose\n // `load()` / `buildCrossStackResolver()` call, which violates the\n // interface contract (the CLI calls `dispose()` in its outer\n // `finally`, so any subsequent provider use is a programming bug).\n // Flip the flag in `dispose()` and throw on any operational entry\n // point so the bug surfaces loudly.\n private disposed = false;\n\n constructor(opts: CfnLocalStateProviderOptions) {\n this.cfnStackName = opts.cfnStackName;\n this.region = opts.region;\n this.clientOptions = { region: opts.region };\n if (opts.profile !== undefined) this.clientOptions.profile = opts.profile;\n }\n\n private getClient(): CloudFormationClient {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n if (!this.client) {\n // Profile threading matches `S3LocalStateProvider` /\n // `S3StateBackend` / `src/utils/aws-region-resolver.ts` — every\n // other cdk-local command already passes the CLI's `--profile` through\n // to the SDK client constructor so the SDK's profile-aware\n // credential resolution picks up the named profile from\n // `~/.aws/credentials` / `~/.aws/config`. Issue #628.\n this.client = new CloudFormationClient({\n region: this.region,\n ...(this.clientOptions.profile !== undefined && { profile: this.clientOptions.profile }),\n });\n }\n return this.client;\n }\n\n private getLambdaClient(): LambdaClient {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n if (!this.lambdaClient) {\n this.lambdaClient = new LambdaClient({\n region: this.region,\n ...(this.clientOptions.profile !== undefined && { profile: this.clientOptions.profile }),\n });\n }\n return this.lambdaClient;\n }\n\n private getSsmClient(): SSMClient {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n if (!this.ssmClient) {\n // Reuse the SAME region + profile threading as the CFn / Lambda\n // clients so the SSM read targets the same account / region the\n // `--from-cfn-stack` state load used (issue #628 / #94). A second\n // credential resolution path would risk reading SSM from the\n // default account while CFn read from the named profile's account.\n this.ssmClient = new SSMClient({\n region: this.region,\n ...(this.clientOptions.profile !== undefined && { profile: this.clientOptions.profile }),\n });\n }\n return this.ssmClient;\n }\n\n /**\n * Resolve the synthesized template's SSM-backed `Parameters`\n * (`AWS::SSM::Parameter::Value<String>` /\n * `AWS::SSM::Parameter::Value<List<String>>`) into a\n * `parameterLogicalId -> value` map via SSM Parameter Store. See\n * {@link LocalStateProvider.resolveTemplateSsmParameters}.\n *\n * Returns an empty map (and opens no SSM client) when the template\n * declares no SSM-backed parameters. Best-effort: SSM failures are\n * absorbed inside {@link resolveSsmParameters} with a per-chunk warn.\n */\n public async resolveTemplateSsmParameters(\n template: CloudFormationTemplate\n ): Promise<ResolvedSsmParameters> {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n const refs = collectSsmParameterRefs(template);\n if (refs.length === 0) return { values: {}, secureStringLogicalIds: [] };\n const client = this.getSsmClient();\n return resolveSsmParameters(client, refs, this.label);\n }\n\n /**\n * Read a deployed Lambda function's already-resolved\n * `Environment.Variables` via `lambda:GetFunctionConfiguration`. See\n * {@link LocalStateProvider.resolveDeployedFunctionEnv} for why this\n * closes the `Fn::GetAtt`/`Fn::Sub`/cross-stack gap for a consumer\n * function's own env vars.\n *\n * Best-effort: on any expected miss (function not found, access\n * denied, throttling) logs a warn and returns `undefined` so the\n * caller falls back to warn-and-drop on the affected keys. Never\n * throws out of the substitution pass.\n */\n public async resolveDeployedFunctionEnv(\n functionPhysicalId: string\n ): Promise<Record<string, string> | undefined> {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n const logger = getLogger();\n const client = this.getLambdaClient();\n try {\n const resp = await client.send(\n new GetFunctionConfigurationCommand({ FunctionName: functionPhysicalId })\n );\n // `Environment.Variables` is the deploy-time-resolved map. Absent\n // when the function declares no env vars — treat as empty so the\n // caller's fallback simply fills nothing.\n return resp.Environment?.Variables ?? {};\n } catch (err) {\n logger.warn(\n `${this.label}: GetFunctionConfiguration(${functionPhysicalId}) failed: ${formatAwsErrorForWarn(err)}. ` +\n `Intrinsic-valued env vars that need the deployed value will warn-and-drop (grant lambda:GetFunctionConfiguration or override via --env-vars).`\n );\n return undefined;\n }\n }\n\n /**\n * Read a deployed Lambda function's execution-role ARN via\n * `lambda:GetFunctionConfiguration`'s `Configuration.Role` field.\n * See {@link LocalStateProvider.resolveLambdaExecutionRoleArn}.\n *\n * Best-effort: on any expected miss (function not found, access\n * denied, throttling) logs a warn and returns `undefined` so the\n * caller falls through to the existing \"Pass the ARN explicitly:\n * --assume-role <arn>\" path. Never throws.\n */\n public async resolveLambdaExecutionRoleArn(\n functionPhysicalId: string\n ): Promise<string | undefined> {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n const logger = getLogger();\n const client = this.getLambdaClient();\n try {\n const resp = await client.send(\n new GetFunctionConfigurationCommand({ FunctionName: functionPhysicalId })\n );\n if (typeof resp.Role === 'string' && resp.Role.startsWith('arn:')) {\n return resp.Role;\n }\n return undefined;\n } catch (err) {\n logger.warn(\n `${this.label}: GetFunctionConfiguration(${functionPhysicalId}) for --assume-role auto-resolve failed: ${formatAwsErrorForWarn(err)}. ` +\n `Pass the ARN explicitly: --assume-role <arn>.`\n );\n return undefined;\n }\n }\n\n /**\n * Load the deployed CFn stack's resources + outputs and return them\n * as a synthetic `LocalStateRecord` (matching the shape the existing\n * S3-state-driven path produces). `synthRegion` is accepted for\n * interface parity with the S3 provider but ignored here — the\n * provider is region-bound at construction time.\n *\n * Best-effort: on any CFn API failure (stack not found, access\n * denied, throttling) the provider logs a warn and returns\n * `undefined`. The caller then falls back to the PR 1 warn-and-drop\n * behavior on every intrinsic-valued env var.\n */\n public async load(\n _stackName: string,\n _synthRegion: string | undefined\n ): Promise<LocalStateRecord | undefined> {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n const logger = getLogger();\n const client = this.getClient();\n\n let resourceMap: Record<string, ResourceState>;\n try {\n const summaries = await fetchAllStackResources(client, this.cfnStackName);\n resourceMap = buildResourceStateMap(summaries);\n } catch (err) {\n logger.warn(\n `${this.label}: ListStackResources(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. ` +\n `Was the stack deployed in region '${this.region}'? Falling back.`\n );\n return undefined;\n }\n\n let outputs: Record<string, string>;\n try {\n const resp = await client.send(new DescribeStacksCommand({ StackName: this.cfnStackName }));\n const stack = resp.Stacks?.[0];\n if (!stack) {\n logger.warn(\n `${this.label}: DescribeStacks(${this.cfnStackName}) returned no stack; outputs will be empty.`\n );\n outputs = {};\n } else {\n outputs = buildOutputsMap(stack.Outputs ?? []);\n }\n } catch (err) {\n logger.warn(\n `${this.label}: DescribeStacks(${this.cfnStackName}) failed: ${formatAwsErrorForWarn(err)}. ` +\n `Outputs will be empty (Fn::GetStackOutput cannot resolve).`\n );\n outputs = {};\n }\n\n return {\n resources: resourceMap,\n outputs,\n region: this.region,\n };\n }\n\n /**\n * Build a `CrossStackResolver` that resolves `Fn::ImportValue` via\n * `cloudformation:ListExports`. `Fn::GetStackOutput` is rejected here\n * — it has no CFn-side equivalent, and the user-visible error message\n * names the right intrinsic (`Fn::ImportValue`) for that use case.\n *\n * `consumerRegion` is accepted for interface parity with the S3\n * provider but the `CfnLocalStateProvider` only resolves exports in\n * the region the stack lives in (which is the same region the\n * consumer Lambda runs in for the common single-region use case).\n * A future PR can extend this to multi-region by walking the SDK's\n * partition-aware region list.\n */\n public async buildCrossStackResolver(\n _consumerRegion: string\n ): Promise<CrossStackResolver | undefined> {\n if (this.disposed) {\n throw new Error('CfnLocalStateProvider used after dispose()');\n }\n const logger = getLogger();\n const client = this.getClient();\n const label = this.label;\n const region = this.region;\n // Memoize the exports map across multiple `Fn::ImportValue` lookups\n // in one substitution pass so a multi-import env block doesn't pay\n // N round-trips to ListExports.\n //\n // Issue #611 fix: cache the in-flight Promise (not just the\n // resolved value) so parallel `resolveImport` callers single-flight\n // through ONE `ListExports` walk. Without this, two awaiting\n // callers both see `cachedExports === undefined` at entry, both\n // fire `fetchAllExports`, and the cache only \"saves\" later\n // sequential callers. The parallel race is realistic: a single\n // env block with two `Fn::ImportValue`s drives the substitution\n // engine through `Promise.all`-shaped concurrent resolver calls.\n let exportsPromise: Promise<Map<string, string> | undefined> | undefined;\n\n const ensureExports = (): Promise<Map<string, string> | undefined> => {\n if (exportsPromise) return exportsPromise;\n exportsPromise = fetchAllExports(client).catch((err: unknown) => {\n logger.warn(\n `${label}: ListExports (${region}) failed: ${formatAwsErrorForWarn(err)}. ` +\n `Fn::ImportValue intrinsics will warn-and-drop.`\n );\n return undefined;\n });\n return exportsPromise;\n };\n\n return {\n async resolveImport(exportName: string): Promise<string | undefined> {\n const map = await ensureExports();\n if (!map) return undefined;\n return map.get(exportName);\n },\n async resolveGetStackOutput(\n producerStack: string,\n producerRegion: string,\n outputName: string\n ): Promise<string | undefined> {\n // `Fn::GetStackOutput` has no CFn-side equivalent (CFn templates\n // use `Fn::ImportValue` + an explicit `Outputs.<name>.Export`);\n // rather than silently returning undefined we surface a\n // logger.warn naming the intrinsic and the producer so the user\n // sees why the env var dropped. The `state-resolver.ts` async\n // path turns the `undefined` into its own per-key warn with the\n // standard \"output not found in producer stack state\" message,\n // so skipping the warn here would mask the nature of the gap.\n logger.warn(\n `${label}: Fn::GetStackOutput '${producerStack}.${outputName}' (${producerRegion}) has no CloudFormation equivalent and cannot be resolved when reading state from a CloudFormation stack. ` +\n `Use Fn::ImportValue against an exported output instead.`\n );\n return undefined;\n },\n };\n }\n\n public dispose(): void {\n this.disposed = true;\n if (this.client) {\n this.client.destroy();\n this.client = undefined;\n }\n if (this.lambdaClient) {\n this.lambdaClient.destroy();\n this.lambdaClient = undefined;\n }\n if (this.ssmClient) {\n this.ssmClient.destroy();\n this.ssmClient = undefined;\n }\n }\n}\n\n/**\n * Build the synthetic per-logical-id resource map from\n * `ListStackResources` output. Each `ResourceState` carries the\n * physical id (covers `Ref`) and the resource type; `attributes` is\n * left empty per issue #606's (a) recommendation — the warn-and-drop\n * policy on unresolvable `Fn::GetAtt` is the v1 contract. The other\n * `ResourceState` fields (`properties`, `dependencies`, etc.) are\n * also left empty since the substituter doesn't read them.\n *\n * Exported for unit testing.\n */\nexport function buildResourceStateMap(\n stackResources: Array<{\n LogicalResourceId?: string | undefined;\n PhysicalResourceId?: string | undefined;\n ResourceType?: string | undefined;\n }>\n): Record<string, ResourceState> {\n const out: Record<string, ResourceState> = {};\n for (const r of stackResources) {\n // CFn occasionally returns half-populated entries for mid-create\n // resources or sentinels like `AWS::CDK::Metadata`. Skip them —\n // they have no usable physical id, and the substituter would\n // report `Ref: <id>` as unresolved with a clearer error.\n if (!r.LogicalResourceId || !r.PhysicalResourceId || !r.ResourceType) {\n continue;\n }\n out[r.LogicalResourceId] = {\n physicalId: r.PhysicalResourceId,\n resourceType: r.ResourceType,\n properties: {},\n attributes: {},\n dependencies: [],\n };\n }\n return out;\n}\n\n/**\n * Build the outputs map from `DescribeStacks.Outputs[]`. CFn outputs\n * are stringly typed at the wire level (key + value, with the value\n * always a string), so the cast is safe.\n *\n * Exported for unit testing.\n */\nexport function buildOutputsMap(\n outputs: Array<{ OutputKey?: string | undefined; OutputValue?: string | undefined }>\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const o of outputs) {\n if (o.OutputKey === undefined || o.OutputValue === undefined) continue;\n out[o.OutputKey] = o.OutputValue;\n }\n return out;\n}\n\n/**\n * Walk `ListStackResources` until every page is consumed and return the\n * full `StackResourceSummary[]`. `ListStackResources` is paginated and\n * returns up to 100 resources per page; the provider must walk every\n * page so stacks with more than 100 resources are mapped completely.\n *\n * This is the reason the provider does not use `DescribeStackResources`:\n * that API caps its response at the first 100 resources with no\n * `NextToken`, so a > 100-resource stack silently loses its tail and the\n * dropped resources' `Ref`s become unresolvable. Exported for unit\n * testing.\n */\nexport async function fetchAllStackResources(\n client: CloudFormationClient,\n stackName: string\n): Promise<\n Array<{\n LogicalResourceId?: string | undefined;\n PhysicalResourceId?: string | undefined;\n ResourceType?: string | undefined;\n }>\n> {\n const out: Array<{\n LogicalResourceId?: string | undefined;\n PhysicalResourceId?: string | undefined;\n ResourceType?: string | undefined;\n }> = [];\n let nextToken: string | undefined;\n // Safety bound — a CFn stack holds at most 500 resources by default\n // (raisable, but far below this ceiling) and ListStackResources returns\n // up to 100 per page, so a real stack needs only a handful of pages.\n // 100 pages defends against a hypothetical NextToken loop without ever\n // tripping on a legitimate stack.\n let pages = 0;\n do {\n const resp = await client.send(\n new ListStackResourcesCommand({\n StackName: stackName,\n ...(nextToken !== undefined && { NextToken: nextToken }),\n })\n );\n for (const summary of resp.StackResourceSummaries ?? []) {\n out.push(summary);\n }\n nextToken = resp.NextToken;\n pages += 1;\n if (pages > 100) {\n throw new Error(\n 'ListStackResources pagination exceeded 100 pages — likely a malformed NextToken loop.'\n );\n }\n // Treat an empty-string NextToken as terminal (mirrors fetchAllExports):\n // the SDK types it as `string | undefined`; AWS should not return `''`\n // but if it does, looping on it would waste round-trips.\n } while (nextToken !== undefined && nextToken !== '');\n return out;\n}\n\n/**\n * Walk `ListExports` until every page is consumed and return the\n * `Name -> Value` map. Same-region only (CFn exports are\n * region-scoped); the caller picks the region at provider\n * construction time.\n *\n * Exported for unit testing.\n */\nexport async function fetchAllExports(client: CloudFormationClient): Promise<Map<string, string>> {\n const out = new Map<string, string>();\n let nextToken: string | undefined;\n // Safety bound — CFn allows at most ~200 exports per account/region,\n // so 50 pages of 100 each is well above the realistic ceiling.\n // Defends against a hypothetical pagination bug returning the same\n // NextToken in a loop.\n let pages = 0;\n do {\n const resp = await client.send(\n new ListExportsCommand({ ...(nextToken !== undefined && { NextToken: nextToken }) })\n );\n for (const exp of resp.Exports ?? []) {\n if (exp.Name === undefined || exp.Value === undefined) continue;\n out.set(exp.Name, exp.Value);\n }\n nextToken = resp.NextToken;\n pages += 1;\n if (pages > 50) {\n throw new Error(\n 'ListExports pagination exceeded 50 pages — likely a malformed NextToken loop.'\n );\n }\n // Issue #611 NIT 3: defend against an empty-string `NextToken`. The\n // SDK type allows `string | undefined`; AWS shouldn't return `''`\n // in practice, but if it ever does the loop would keep firing\n // `ListExports({ NextToken: '' })` until the 50-page bound — wasted\n // round-trips on an empty result page. Treat `''` as terminal.\n } while (nextToken !== undefined && nextToken !== '');\n return out;\n}\n\n/**\n * Format an AWS SDK error as `<name> (HTTP <status>): <message>` so the\n * surfaced warn name the error class (e.g. `ThrottlingException`,\n * `AccessDeniedException`, `ValidationError`) and HTTP status alongside\n * the human-readable message. Falls back to the bare message for\n * non-SDK errors (the existing pre-issue-#611 behavior) so non-AWS\n * thrown values still surface meaningfully. Exported for unit testing.\n *\n * Issue #611 NIT 4 — `normalizeAwsError` in `utils/error-handler.ts` is\n * S3-bucket-specific (it rewrites the synthetic `Unknown`/`UnknownError`\n * with bucket / region context), so the CFn provider extracts the\n * pieces directly here.\n */\nexport function formatAwsErrorForWarn(err: unknown): string {\n if (!(err instanceof Error)) return String(err);\n const name = err.name && err.name !== 'Error' ? err.name : undefined;\n const status = (err as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode;\n const prefixParts: string[] = [];\n if (name !== undefined) prefixParts.push(name);\n if (status !== undefined) prefixParts.push(`HTTP ${status}`);\n if (prefixParts.length === 0) return err.message;\n return `${prefixParts.join(' ')}: ${err.message}`;\n}\n","import { STSClient } from '@aws-sdk/client-sts';\n\n/**\n * Resolve the AWS region the SDK would pick for a named profile (the\n * profile's `region` in `~/.aws/config`, honoring the standard\n * env / config chain) WITHOUT resolving credentials.\n *\n * cdk-local already passes `--profile` straight to the AWS SDK clients\n * for CREDENTIALS, but the `--from-cfn-stack` region resolution\n * (`resolveCfnRegion`) only consults `--stack-region` / `--region` /\n * `AWS_REGION` / `AWS_DEFAULT_REGION` / the synth-derived stack region.\n * A profile that carries a `region` (e.g. `region = ap-northeast-1`)\n * was therefore ignored, so `cdkl ... --from-cfn-stack --profile <p>`\n * against an env-agnostic stack failed with \"requires a region to query\n * CloudFormation\" even though `aws cloudformation ... --profile <p>`\n * would have used the profile's region.\n *\n * Uses the same SDK region provider as\n * `resolveProfileCredentials` (an STS client's resolved `config.region`)\n * so the fallback region matches what the CFn client itself would pick\n * for the profile. Best-effort: returns `undefined` when no profile is\n * given, or the region cannot be resolved (no profile region and no\n * `AWS_REGION` / `AWS_DEFAULT_REGION` env) — the caller then falls\n * through to its existing \"no region\" handling.\n */\nexport async function resolveProfileRegion(\n profile: string | undefined\n): Promise<string | undefined> {\n if (profile === undefined || profile === '') return undefined;\n const sts = new STSClient({ profile });\n try {\n const regionProvider = sts.config.region;\n const resolved = typeof regionProvider === 'function' ? await regionProvider() : regionProvider;\n return typeof resolved === 'string' && resolved.length > 0 ? resolved : undefined;\n } catch {\n return undefined;\n } finally {\n sts.destroy();\n }\n}\n","/**\n * Single-source-of-truth helper that picks a {@link LocalStateProvider}\n * for the `cdkl *` command family from CLI flags.\n *\n * Built-in flag (always wired):\n *\n * - `--from-cfn-stack [<cfn-stack-name>]` — CFn-backed; reads a\n * deployed CloudFormation stack via `ListStackResources`.\n *\n * Host-extensible state sources (via the `extraStateProviders` option):\n *\n * - Hosts embedding cdk-local can register additional `LocalStateProvider`\n * factories that respond to their own CLI flags (e.g. a host-registered\n * `--from-state` backed by an S3 state store). Each entry is keyed by\n * the camel-case Commander option name (e.g. `'fromState'`) so the\n * dispatcher reads the corresponding boolean / string off the parsed\n * options bag.\n *\n * This module centralizes:\n *\n * - The mutual-exclusion check across `--from-cfn-stack` and every\n * registered extra state-provider flag.\n * - The bare-vs-explicit `--from-cfn-stack` resolution: bare flag uses\n * the stack name from synthesis; an explicit value overrides.\n * - Region resolution for the CFn client: precedence\n * `--stack-region` > `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION`\n * > the synth-derived stack region > the `--profile`'s configured\n * region (see {@link resolveCfnFallbackRegion}).\n *\n * Returns `undefined` when no state-source flag is set — the caller\n * skips the substitution pass entirely.\n */\n\nimport { CfnLocalStateProvider } from '../../local/cfn-local-state-provider.js';\nimport { getEmbedConfig } from '../../local/embed-config.js';\nimport { resolveProfileRegion } from '../../utils/profile-region.js';\nimport type { LocalStateProvider } from '../../local/local-state-provider.js';\n\n/**\n * Options each `cdkl` command gathers from its flag set. The built-in\n * `--from-cfn-stack` flag is always present; the host may add fields\n * for its own `extraStateProviders` entries (e.g. `fromState: boolean`\n * for a host's `--from-state` shim).\n */\nexport interface LocalStateSourceOptions {\n /**\n * `--from-cfn-stack` flag value. Commander maps:\n * - flag absent → `undefined`\n * - `--from-cfn-stack` (bare) → `true`\n * - `--from-cfn-stack <name>` → `'<name>'`\n */\n fromCfnStack?: string | boolean;\n /** Inherited `--region`. */\n region?: string;\n /** Inherited `--profile`. */\n profile?: string;\n /**\n * Inherited `--stack-region`. Used by `--from-cfn-stack` as the CFn\n * client's region. When unset, the helper falls back to `--region` >\n * `AWS_REGION` > `AWS_DEFAULT_REGION` > the synth-derived stack region.\n */\n stackRegion?: string;\n /** Arbitrary host-injected fields read by `extraStateProviders` factories. */\n [key: string]: unknown;\n}\n\n/**\n * Factory function signature for a host-supplied state provider. The\n * dispatcher invokes the matching factory with the full parsed options\n * bag so the factory can read its own option fields directly.\n */\nexport type LocalStateProviderFactory = (options: LocalStateSourceOptions) => LocalStateProvider;\n\n/**\n * Registry of host-supplied state-provider factories.\n *\n * Each key is the camel-case Commander option name (e.g. `'fromState'`)\n * that the dispatcher should treat as \"this state source is active when\n * the corresponding option field is truthy\". When activated, the\n * dispatcher invokes the factory and returns its result.\n */\nexport type ExtraStateProviders = Record<string, LocalStateProviderFactory>;\n\n/**\n * Default stack name → CFn stack name resolution. Bare `--from-cfn-stack`\n * uses the cdkl stack name verbatim as the CFn stack name (typical for\n * CDK apps where the names match). Override by passing\n * `--from-cfn-stack <explicit-name>`.\n *\n * Exported for unit testing.\n */\nexport function resolveCfnStackName(fromCfnStack: string | boolean, stackName: string): string {\n if (typeof fromCfnStack === 'string') return fromCfnStack;\n return stackName;\n}\n\n/**\n * Single source of truth for \"is the user asking for `--from-cfn-stack`?\".\n * Commander maps the flag to one of `undefined` (absent) / `true` (bare) /\n * `'<name>'` (explicit). Everything except `undefined` / `false` means\n * the flag is present.\n *\n * Exported for use by `cdkl start-api` and unit testing.\n */\nexport function isCfnFlagPresent(opts: Pick<LocalStateSourceOptions, 'fromCfnStack'>): boolean {\n const v = opts.fromCfnStack;\n return v !== undefined && v !== false;\n}\n\n/**\n * Resolve the region used for the CFn client. The CFn provider is\n * region-bound at construction time; we apply the precedence chain\n * `--stack-region` > `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION`\n * > the synth-derived stack region. Throws `LocalStateSourceError`\n * when none of these signals is set — the CFn API call needs a\n * concrete region and silently picking `us-east-1` would query the\n * wrong stack environment.\n *\n * Exported for unit testing.\n */\nexport function resolveCfnRegion(\n options: Pick<LocalStateSourceOptions, 'stackRegion' | 'region'>,\n synthRegion: string | undefined\n): string {\n const region =\n options.stackRegion ??\n options.region ??\n process.env['AWS_REGION'] ??\n process.env['AWS_DEFAULT_REGION'] ??\n synthRegion;\n if (region === undefined) {\n throw new LocalStateSourceError(\n '--from-cfn-stack requires a region to query CloudFormation. ' +\n 'Set one of: --stack-region <region>, --region <region>, AWS_REGION env var, AWS_DEFAULT_REGION env var, or an env.region on the target CDK stack.'\n );\n }\n return region;\n}\n\n/**\n * Compute the lowest-precedence region fallback for the CFn state\n * provider and pass the result as the `synthRegion` argument to\n * {@link createLocalStateProvider}. The synth-derived stack region wins\n * when present; otherwise — and only when `--from-cfn-stack` is active —\n * the `--profile`'s configured region is used so an env-agnostic stack\n * (no `env.region`) is still queryable via `--profile <p>` alone, the\n * same way `aws cloudformation ... --profile <p>` resolves region from\n * the profile.\n *\n * {@link resolveCfnRegion}'s precedence (`--stack-region` > `--region` >\n * `AWS_REGION` > `AWS_DEFAULT_REGION` > this fallback) is preserved: the\n * profile region only matters when every higher-priority signal is\n * absent.\n *\n * Async because reading the profile region touches the shared config\n * files; the public sync `resolveCfnRegion` / `createLocalStateProvider`\n * signatures are intentionally left unchanged so embedding hosts are not\n * forced to adopt an async region resolver.\n */\nexport async function resolveCfnFallbackRegion(\n options: Pick<LocalStateSourceOptions, 'fromCfnStack' | 'profile'>,\n synthRegion: string | undefined\n): Promise<string | undefined> {\n if (synthRegion !== undefined) return synthRegion;\n if (!isCfnFlagPresent(options)) return undefined;\n return resolveProfileRegion(options.profile);\n}\n\n/**\n * Common error class for the mutual-exclusion check so the CLI layer\n * can surface a consistent error message from every command.\n */\nexport class LocalStateSourceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LocalStateSourceError';\n }\n}\n\n/**\n * Pre-flight check for `--from-cfn-stack <explicit-name>` when the\n * caller will construct one provider per routed stack (e.g.\n * `cdkl start-api` / `cdkl start-service`). An explicit value applies\n * to the SINGLE CFn stack named — when multiple stacks are routed,\n * every one of them would query the same CFn stack, yielding silent\n * wrong-physical-id substitutions for any logical id that happens to\n * collide between the user's stacks. Reject at the CLI layer instead.\n *\n * Bare `--from-cfn-stack` (the stack-name-default) is fine for\n * multi-stack: each routed stack reads its own CFn counterpart.\n */\nexport function rejectExplicitCfnStackWithMultipleStacks(\n options: Pick<LocalStateSourceOptions, 'fromCfnStack'>,\n routedStackCount: number\n): void {\n if (routedStackCount <= 1) return;\n if (typeof options.fromCfnStack !== 'string') return;\n throw new LocalStateSourceError(\n `--from-cfn-stack <name> cannot be used with multiple routed stacks (got ${routedStackCount}). ` +\n 'An explicit CFn stack name applies to one stack only and would silently mismap logical IDs across siblings. ' +\n `Use bare --from-cfn-stack (each stack uses its own name as the CFn stack name) or run one ${getEmbedConfig().binaryName} invocation per stack.`\n );\n}\n\n/**\n * Pick and construct the right `LocalStateProvider` for the supplied\n * flag set. Returns `undefined` when no state-source flag is set\n * (caller skips the substitution pass). Throws `LocalStateSourceError`\n * when more than one state-source is active (mutually exclusive —\n * different sources, asking for several is ambiguous about precedence).\n *\n * `stackName` is the cdkl-side stack name the command resolved to its\n * target — needed for the bare-`--from-cfn-stack` default. `synthRegion`\n * is the synth-derived stack region (`env.region` on the CDK stack) —\n * fallback for the CFn client when no explicit region override is set.\n *\n * `extraStateProviders` is the host-supplied registry of additional\n * state sources (e.g. a host's `--from-state` / S3-backed provider).\n * Each entry's key is the camel-case Commander option name; the\n * dispatcher activates the matching factory when the corresponding\n * options field is truthy.\n *\n * For multi-stack callers (`cdkl start-api` / `cdkl start-service`)\n * also invoke `rejectExplicitCfnStackWithMultipleStacks` BEFORE the\n * per-stack loop.\n */\nexport function createLocalStateProvider(\n options: LocalStateSourceOptions,\n stackName: string,\n synthRegion: string | undefined,\n extraStateProviders?: ExtraStateProviders\n): LocalStateProvider | undefined {\n const cfnStackOpt = options.fromCfnStack;\n const cfnFlagPresent = isCfnFlagPresent(options);\n\n // Mutual-exclusion: count active state sources. Both --from-cfn-stack\n // and every host-registered extra flag (e.g. a host's --from-state) are\n // counted; the user must pick exactly one.\n const activeExtras: string[] = [];\n if (extraStateProviders) {\n for (const key of Object.keys(extraStateProviders)) {\n if (options[key]) {\n activeExtras.push(key);\n }\n }\n }\n if (cfnFlagPresent && activeExtras.length > 0) {\n throw new LocalStateSourceError(\n `--from-cfn-stack is mutually exclusive with ${activeExtras.map(toFlagName).join(', ')}. ` +\n 'Pick one state source.'\n );\n }\n if (activeExtras.length > 1) {\n throw new LocalStateSourceError(\n `state-source flags are mutually exclusive: ${activeExtras.map(toFlagName).join(', ')}. ` +\n 'Pick one.'\n );\n }\n\n // Reject empty `--from-cfn-stack \"\"`. Letting it through would\n // construct a `CfnLocalStateProvider` with `cfnStackName: ''` and the\n // SDK's `ListStackResources({ StackName: '' })` rejects with a\n // generic ValidationError far from the call site. Reject at the\n // dispatcher with a clear remediation message instead.\n if (cfnStackOpt === '') {\n throw new LocalStateSourceError(\n '--from-cfn-stack requires a non-empty stack name when an explicit value is provided. ' +\n 'Drop the value to use the resolved stack name, or pass --from-cfn-stack <name>.'\n );\n }\n\n if (cfnFlagPresent) {\n const cfnStackName = resolveCfnStackName(cfnStackOpt as string | boolean, stackName);\n const region = resolveCfnRegion(options, synthRegion);\n return new CfnLocalStateProvider({\n cfnStackName,\n region,\n ...(options.profile !== undefined && { profile: options.profile }),\n });\n }\n\n if (activeExtras.length === 1) {\n const key = activeExtras[0]!;\n const factory = extraStateProviders![key]!;\n return factory(options);\n }\n\n return undefined;\n}\n\n/** Convert a camel-case option field name to its `--kebab-case` flag form. */\nfunction toFlagName(field: string): string {\n return '--' + field.replace(/([A-Z])/g, '-$1').toLowerCase();\n}\n","import { createWriteStream, rmSync } from 'node:fs';\nimport { mkdir, mkdtemp } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, join, normalize, resolve } from 'node:path';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { getLogger } from '../utils/logger.js';\nimport type { ResolvedArnLambdaLayer } from './lambda-resolver.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Materialize a literal-ARN Lambda Layer to a host tmpdir so it can be\n * bind-mounted at `/opt` alongside same-stack layers (issue #448).\n *\n * Steps:\n *\n * 1. Optional `sts:AssumeRole` against `roleArn` (the CLI's\n * `--layer-role-arn <arn>` flag). When the dev's default\n * credentials cannot read the layer (cross-account case) the role\n * typically belongs to a trust-policy-permitted role in the layer's\n * account.\n * 2. `lambda:GetLayerVersion` against the layer's region (parsed from\n * the ARN by `parseLayerVersionArn` — NOT the dev's profile\n * region) to recover the presigned S3 URL in `Content.Location`.\n * 3. Download the ZIP from the presigned URL via `fetch(...)` (no AWS\n * credentials needed on the GET — the presign carries them).\n * 4. Unzip into a fresh tmpdir under `os.tmpdir()` using `node:zlib`\n * + the documented ZIP-file format. AWS layer ZIPs use the\n * DEFLATE compression method.\n *\n * Returns the absolute path to the unzipped directory; the caller\n * `cpSync`-merges it into the `/opt` host tmpdir alongside any\n * same-stack `kind: 'asset'` layers and records the path in the\n * tracking set for cleanup.\n *\n * Failures surface as `LayerMaterializationError` with the layer ARN\n * in the message so the user sees which layer broke (vs which\n * Lambda's `Properties.Layers` array hit which AWS error).\n *\n * **Network IO is gated by the `lambdaClientFactory` / `stsClientFactory`\n * options** to keep unit tests deterministic — production callers omit\n * both and the function builds the real SDK clients on the fly via\n * dynamic `import()` to keep the cold-start path small.\n */\nexport interface MaterializeLayerOptions {\n /**\n * Optional role to assume before calling `GetLayerVersion`. When\n * unset the dev's default credentials (whatever the SDK default\n * chain resolves) are used. Threading a per-CLI-invocation flag is\n * the canonical cross-account escape hatch — see `--layer-role-arn`\n * on `cdkl invoke` / `cdkl start-api`.\n */\n roleArn?: string;\n /**\n * Test seam: override the Lambda client (the production call goes\n * through `@aws-sdk/client-lambda`'s `LambdaClient.send(new\n * GetLayerVersionCommand(...))`).\n */\n lambdaClientFactory?: (region: string, credentials?: AwsCredentials) => LambdaSendClient;\n /**\n * Test seam: override the STS client (production goes through\n * `@aws-sdk/client-sts`'s `STSClient.send(new AssumeRoleCommand(...))`).\n */\n stsClientFactory?: (region: string) => StsSendClient;\n /**\n * Test seam: override the presigned-URL ZIP fetch. The production\n * call uses Node's built-in `fetch()`. Returns a `Uint8Array` (the\n * ZIP body) so the test can inject a fixture-built ZIP.\n */\n fetchZip?: (presignedUrl: string) => Promise<Uint8Array>;\n}\n\nexport interface AwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n}\n\n/**\n * Minimal slice of `LambdaClient` cdk-local needs. Surfaced as an interface\n * so unit tests can mock without pulling the real SDK module.\n */\nexport interface LambdaSendClient {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n send(command: any): Promise<{ Content?: { Location?: string } }>;\n destroy?: () => void;\n}\n\nexport interface StsSendClient {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n send(command: any): Promise<{\n Credentials?: {\n AccessKeyId?: string;\n SecretAccessKey?: string;\n SessionToken?: string;\n };\n }>;\n destroy?: () => void;\n}\n\nexport class LayerMaterializationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LayerMaterializationError';\n Object.setPrototypeOf(this, LayerMaterializationError.prototype);\n }\n}\n\nexport async function materializeLayerFromArn(\n layer: ResolvedArnLambdaLayer,\n options: MaterializeLayerOptions = {}\n): Promise<string> {\n const logger = getLogger();\n\n let credentials: AwsCredentials | undefined;\n if (options.roleArn) {\n try {\n credentials = await assumeRoleForLayer(options.roleArn, layer.region, options);\n logger.debug(`Layer ${layer.arn}: assumed role ${options.roleArn} for GetLayerVersion`);\n } catch (err) {\n throw new LayerMaterializationError(\n `Layer ${layer.arn}: STS AssumeRole(${options.roleArn}) failed: ${errMsg(err)}. ` +\n 'Check the role trust policy permits your principal and sts:AssumeRole is allowed.'\n );\n }\n }\n\n let presignedUrl: string;\n try {\n presignedUrl = await fetchLayerContentUrl(layer, credentials, options);\n } catch (err) {\n const hint = looksLikeAccessDenied(err)\n ? ' GetLayerVersion access denied; check the credentials / role can read the layer ' +\n '(grant lambda:GetLayerVersion on the layer ARN, or pass --layer-role-arn <arn> ' +\n 'to assume a role in the layer account).'\n : '';\n throw new LayerMaterializationError(\n `Layer ${layer.arn}: GetLayerVersion failed in region ${layer.region}: ${errMsg(err)}.${hint}`\n );\n }\n\n let zipBytes: Uint8Array;\n try {\n zipBytes = await downloadPresignedZip(presignedUrl, options);\n } catch (err) {\n throw new LayerMaterializationError(\n `Layer ${layer.arn}: failed to download layer ZIP from the presigned URL: ${errMsg(err)}.`\n );\n }\n\n const dir = await mkdtemp(\n join(\n tmpdir(),\n `${getEmbedConfig().resourceNamePrefix}-arn-layer-${layer.name}-${layer.version}-`\n )\n );\n try {\n await unzipBufferToDirectory(zipBytes, dir);\n } catch (err) {\n // Clean up the partially-extracted tmpdir before re-throwing — the\n // caller never receives `dir` on this path so its tracking sets in\n // local-invoke (ImagePlan.layerArnTmpDirs) / local-start-api\n // (layerTmpDirs Set) never learn about it, and the OS never\n // reclaims it until reboot. Best-effort: a second failure would\n // mask the original unzip error.\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch {\n // best-effort\n }\n throw new LayerMaterializationError(\n `Layer ${layer.arn}: failed to unzip layer contents into '${dir}': ${errMsg(err)}.`\n );\n }\n return dir;\n}\n\nasync function fetchLayerContentUrl(\n layer: ResolvedArnLambdaLayer,\n credentials: AwsCredentials | undefined,\n options: MaterializeLayerOptions\n): Promise<string> {\n const factory = options.lambdaClientFactory ?? (await defaultLambdaClientFactory());\n const client = factory(layer.region, credentials);\n try {\n // Layer ARN form used as LayerName lets the SDK resolve cross-\n // account references without a separate account-id flag. AWS docs:\n // \"When provided the layer-version's ARN as LayerName, the\n // VersionNumber must still be set.\"\n const versionLessArn = `arn:aws:lambda:${layer.region}:${layer.accountId}:layer:${layer.name}`;\n const command = await buildGetLayerVersionCommand(versionLessArn, Number(layer.version));\n const response = await client.send(command);\n const url = response?.Content?.Location;\n if (!url || typeof url !== 'string') {\n throw new Error(\n 'GetLayerVersion response did not include Content.Location (presigned ZIP URL)'\n );\n }\n return url;\n } finally {\n client.destroy?.();\n }\n}\n\nasync function assumeRoleForLayer(\n roleArn: string,\n region: string,\n options: MaterializeLayerOptions\n): Promise<AwsCredentials> {\n const factory = options.stsClientFactory ?? (await defaultStsClientFactory());\n const client = factory(region);\n try {\n const command = await buildAssumeRoleCommand(roleArn);\n const response = await client.send(command);\n const creds = response?.Credentials;\n if (!creds?.AccessKeyId || !creds.SecretAccessKey) {\n throw new Error('AssumeRole returned no Credentials');\n }\n return {\n accessKeyId: creds.AccessKeyId,\n secretAccessKey: creds.SecretAccessKey,\n ...(creds.SessionToken !== undefined && { sessionToken: creds.SessionToken }),\n };\n } finally {\n client.destroy?.();\n }\n}\n\nasync function defaultLambdaClientFactory(): Promise<\n (region: string, credentials?: AwsCredentials) => LambdaSendClient\n> {\n const { LambdaClient } = await import('@aws-sdk/client-lambda');\n return (region, credentials) =>\n new LambdaClient({\n region,\n ...(credentials && {\n credentials: {\n accessKeyId: credentials.accessKeyId,\n secretAccessKey: credentials.secretAccessKey,\n ...(credentials.sessionToken !== undefined && {\n sessionToken: credentials.sessionToken,\n }),\n },\n }),\n });\n}\n\nasync function defaultStsClientFactory(): Promise<(region: string) => StsSendClient> {\n const { STSClient } = await import('@aws-sdk/client-sts');\n return (region) => new STSClient({ region });\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nasync function buildGetLayerVersionCommand(layerArn: string, versionNumber: number): Promise<any> {\n const { GetLayerVersionCommand } = await import('@aws-sdk/client-lambda');\n return new GetLayerVersionCommand({ LayerName: layerArn, VersionNumber: versionNumber });\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nasync function buildAssumeRoleCommand(roleArn: string): Promise<any> {\n const { AssumeRoleCommand } = await import('@aws-sdk/client-sts');\n return new AssumeRoleCommand({\n RoleArn: roleArn,\n RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-layer-${Date.now()}`,\n DurationSeconds: 3600,\n });\n}\n\nasync function downloadPresignedZip(\n presignedUrl: string,\n options: MaterializeLayerOptions\n): Promise<Uint8Array> {\n if (options.fetchZip) return options.fetchZip(presignedUrl);\n const response = await fetch(presignedUrl);\n if (!response.ok) {\n throw new Error(\n `HTTP ${response.status} ${response.statusText} from layer Content.Location URL`\n );\n }\n const buf = await response.arrayBuffer();\n return new Uint8Array(buf);\n}\n\n/**\n * Minimal ZIP unzipper that handles the subset of the ZIP format Lambda\n * layer ZIPs ever use (DEFLATE compression method 8, STORE method 0).\n * Avoids bringing in a heavyweight dep for a 50-line task.\n *\n * Path-traversal guard: every entry's relative path is `normalize()`d\n * and rejected if the resulting absolute path escapes `destDir` (the\n * \"Zip Slip\" CVE class). Symlinks inside the ZIP are also rejected for\n * the same reason — they could point at arbitrary host paths.\n */\nasync function unzipBufferToDirectory(zipBytes: Uint8Array, destDir: string): Promise<void> {\n const view = new DataView(zipBytes.buffer, zipBytes.byteOffset, zipBytes.byteLength);\n // Find the End of Central Directory record (signature 0x06054b50) by\n // scanning the last ~64KB of the buffer backwards.\n const eocdSig = 0x06054b50;\n const maxComment = 0xffff;\n const minScan = Math.max(0, zipBytes.byteLength - maxComment - 22);\n let eocdOffset = -1;\n for (let i = zipBytes.byteLength - 22; i >= minScan; i--) {\n if (view.getUint32(i, true) === eocdSig) {\n eocdOffset = i;\n break;\n }\n }\n if (eocdOffset < 0) {\n throw new Error('Not a ZIP file (no End of Central Directory record found)');\n }\n const totalEntries = view.getUint16(eocdOffset + 10, true);\n const cdSize = view.getUint32(eocdOffset + 12, true);\n const cdOffset = view.getUint32(eocdOffset + 16, true);\n\n const destAbsolute = resolve(destDir);\n let cursor = cdOffset;\n const cdEnd = cdOffset + cdSize;\n let parsed = 0;\n while (cursor < cdEnd && parsed < totalEntries) {\n if (view.getUint32(cursor, true) !== 0x02014b50) {\n throw new Error(`Corrupt ZIP: missing Central Directory header at offset ${cursor}`);\n }\n const compressionMethod = view.getUint16(cursor + 10, true);\n const compressedSize = view.getUint32(cursor + 20, true);\n const uncompressedSize = view.getUint32(cursor + 24, true);\n const fileNameLength = view.getUint16(cursor + 28, true);\n const extraFieldLength = view.getUint16(cursor + 30, true);\n const fileCommentLength = view.getUint16(cursor + 32, true);\n const externalAttrs = view.getUint32(cursor + 38, true);\n const localHeaderOffset = view.getUint32(cursor + 42, true);\n const fileName = new TextDecoder('utf-8').decode(\n zipBytes.subarray(cursor + 46, cursor + 46 + fileNameLength)\n );\n cursor += 46 + fileNameLength + extraFieldLength + fileCommentLength;\n parsed++;\n\n // Reject anything that escapes destDir (Zip Slip).\n const normalized = normalize(fileName);\n const targetPath = resolve(destAbsolute, normalized);\n if (!targetPath.startsWith(destAbsolute + (destAbsolute.endsWith('/') ? '' : '/'))) {\n throw new Error(\n `Refusing to extract entry '${fileName}' — path escapes the destination directory`\n );\n }\n // Symlink entries on Unix encode 0xA in the high byte of external\n // attributes (Unix `mode & S_IFMT >> 16` => 0xA000). Rejected\n // because they could redirect to arbitrary host paths.\n const unixMode = (externalAttrs >>> 16) & 0xffff;\n if ((unixMode & 0xf000) === 0xa000) {\n throw new Error(`Refusing to extract symlink entry '${fileName}' from layer ZIP (security)`);\n }\n\n if (fileName.endsWith('/')) {\n await mkdir(targetPath, { recursive: true });\n continue;\n }\n await mkdir(dirname(targetPath), { recursive: true });\n\n // Read the Local File Header to locate the actual data payload.\n if (view.getUint32(localHeaderOffset, true) !== 0x04034b50) {\n throw new Error(`Corrupt ZIP: missing Local File Header for '${fileName}'`);\n }\n const lfhFileNameLength = view.getUint16(localHeaderOffset + 26, true);\n const lfhExtraFieldLength = view.getUint16(localHeaderOffset + 28, true);\n const dataOffset = localHeaderOffset + 30 + lfhFileNameLength + lfhExtraFieldLength;\n const compressedData = zipBytes.subarray(dataOffset, dataOffset + compressedSize);\n\n let payload: Uint8Array;\n if (compressionMethod === 0) {\n payload = compressedData;\n } else if (compressionMethod === 8) {\n payload = await inflateRaw(compressedData);\n } else {\n throw new Error(\n `Unsupported ZIP compression method ${compressionMethod} for entry '${fileName}' (only STORE and DEFLATE supported)`\n );\n }\n if (payload.length !== uncompressedSize && compressionMethod !== 0) {\n throw new Error(\n `ZIP entry '${fileName}': inflate produced ${payload.length} bytes, expected ${uncompressedSize}`\n );\n }\n // Stream the payload through fs.createWriteStream so we never hold\n // a 100MB+ layer ZIP entirely in memory after the network read.\n await pipeline(Readable.from(payload), createWriteStream(targetPath));\n }\n}\n\nasync function inflateRaw(data: Uint8Array): Promise<Uint8Array> {\n const { inflateRaw: inflate } = await import('node:zlib');\n return new Promise((resolveP, rejectP) => {\n inflate(data, (err, out) => {\n if (err) rejectP(err);\n else resolveP(out);\n });\n });\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nfunction looksLikeAccessDenied(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n const name = (err as { name?: string }).name ?? '';\n const code = (err as { Code?: string }).Code ?? '';\n const message = err.message ?? '';\n return (\n name === 'AccessDeniedException' ||\n code === 'AccessDeniedException' ||\n /access denied/i.test(message) ||\n /not authorized/i.test(message)\n );\n}\n","/**\n * Resolve a Lambda function's `Properties.Environment.Variables` for\n * `cdkl invoke`.\n *\n * Per the issue scope, v1 supports **literal values only**. Intrinsic\n * functions (`Ref` / `Fn::GetAtt` / `Fn::Sub` / etc.) in env vars are\n * unresolvable without state — they would substitute to whatever the\n * deployed value is, and we have no source for that here. Each unresolved\n * key is recorded in `unresolved` so the caller can warn the user; the\n * variable is **dropped** rather than silently substituted with garbage\n * (a bad env var that \"exists\" is harder to debug than one that's missing).\n *\n * `--env-vars <file>` overrides match SAM's shape (D5), with the\n * additional CDK ergonomics that a function-specific entry can be keyed\n * by either the CloudFormation logical ID or the CDK display path\n * (`Metadata['aws:cdk:path']`):\n *\n * {\n * \"Parameters\": { \"GLOBAL_KEY\": \"value\" },\n * \"MyHandlerLogicalId\": { \"FUNCTION_KEY\": \"value\" },\n * \"MyStack/MyHandler\": { \"FUNCTION_KEY\": \"value\" }\n * }\n *\n * Override merge order (lowest to highest priority):\n * 1. Template literal env vars\n * 2. `Parameters` (global, applied to every invoke)\n * 3. Function-specific entries (logical-ID or display-path keyed)\n * applied in JSON insertion order, so a later key wins on conflict.\n * This matches SAM's apply-in-order semantics; pick one form per\n * function to avoid surprises.\n *\n * The override file may also clear a variable by setting it to `null`\n * (matches SAM behavior).\n */\n\nexport interface EnvResolutionResult {\n /** Variables that should be set on the container. */\n resolved: Record<string, string>;\n /** Template env-var keys whose value was an intrinsic and was dropped. */\n unresolved: string[];\n}\n\nexport interface EnvOverrideFile {\n /** Variables applied to every function invocation. */\n Parameters?: Record<string, string | null>;\n /**\n * Function-specific overrides keyed by either the CloudFormation\n * logical ID or the CDK display path (`Metadata['aws:cdk:path']`).\n */\n [logicalIdOrDisplayPath: string]: Record<string, string | null> | undefined;\n}\n\n/**\n * Resolve Lambda env vars for local invocation.\n *\n * @param logicalId The function's CloudFormation logical ID. Used\n * to look up function-specific overrides in the\n * `--env-vars` file.\n * @param displayPath The function's CDK display path\n * (`Metadata['aws:cdk:path']`, e.g.\n * `\"MyStack/MyHandler\"`), or `undefined` when\n * the resource has no path metadata. Display-path\n * keys in the override file are matched against\n * this value in addition to `logicalId`. Pass\n * `undefined` rather than the logical ID when no\n * path is known, so the override lookup does not\n * accidentally double-match the same key.\n * @param templateEnv The function's `Properties.Environment.Variables`\n * object from the synthesized template, or\n * `undefined` when the function has no env vars.\n * @param overrides Parsed `--env-vars` file contents, or\n * `undefined` when the flag was not passed.\n */\nexport function resolveEnvVars(\n logicalId: string,\n displayPath: string | undefined,\n templateEnv: Record<string, unknown> | undefined,\n overrides?: EnvOverrideFile\n): EnvResolutionResult {\n const resolved: Record<string, string> = {};\n const unresolved: string[] = [];\n\n if (templateEnv) {\n for (const [key, value] of Object.entries(templateEnv)) {\n if (isLiteralEnvValue(value)) {\n resolved[key] = String(value);\n } else {\n unresolved.push(key);\n }\n }\n }\n\n if (overrides) {\n applyOverrideMap(resolved, overrides.Parameters);\n // Iterate non-Parameters keys in JSON insertion order so a\n // logical-ID + display-path collision applies later-wins (SAM-compat).\n //\n // Display-path matching mirrors the prefix rule the rest of cdk-local\n // uses for `cdkl invoke <target>` (`src/cli/cdk-path.ts`\n // `resolveCdkPathToLogicalIds`): an override key matches when the\n // resource's `aws:cdk:path` is exactly that key OR starts with\n // `key + \"/\"`. That lets a user write `MyStack/MyFn` (the L2 form\n // they read from CDK app code, same form `cdkl invoke` accepts) and\n // have it match the synthesized L1 resource at `MyStack/MyFn/Resource`\n // — without forcing them to look up the `/Resource` suffix.\n for (const [key, val] of Object.entries(overrides)) {\n if (key === 'Parameters') continue;\n if (!val || typeof val !== 'object') continue;\n if (key === logicalId) {\n applyOverrideMap(resolved, val);\n continue;\n }\n if (displayPath && (displayPath === key || displayPath.startsWith(`${key}/`))) {\n applyOverrideMap(resolved, val);\n }\n }\n }\n\n return { resolved, unresolved };\n}\n\n/**\n * Apply one override map to the accumulator. `null` clears a key (SAM\n * compatibility); any other value is coerced to string. Unknown shapes are\n * silently skipped — the file format is loose and we don't want to fail a\n * whole run on one bad entry.\n */\nfunction applyOverrideMap(\n acc: Record<string, string>,\n map: Record<string, string | null> | undefined\n): void {\n if (!map) return;\n for (const [key, value] of Object.entries(map)) {\n if (value === null) {\n delete acc[key];\n } else if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n acc[key] = String(value);\n }\n }\n}\n\n/**\n * A value is \"literal\" iff it's a string / number / boolean. CFn intrinsic\n * functions are encoded as objects with a single key starting with `Fn::`\n * (or the special-case `Ref`). Anything that isn't a primitive is treated\n * as unresolvable — there is no safe way to substitute an object/array\n * into a Linux env var.\n */\nfunction isLiteralEnvValue(value: unknown): value is string | number | boolean {\n return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';\n}\n","/**\n * State-driven env-var resolution for `cdkl invoke --from-state`.\n *\n * The PR 1 env-resolver classifies any non-literal env-var value (a CFn\n * intrinsic like `Ref` / `Fn::GetAtt` / `Fn::Sub`) as \"unresolved\" and\n * drops it. That's correct when there's no source of truth for the\n * deployed value — which is also the SAM behavior — but it's wrong when\n * cdk-local has already deployed the stack and the AWS-current physical IDs\n * sit in the cdk-local state file.\n *\n * `--from-state` closes that gap: it loads the host's S3 state for the target\n * stack and substitutes `Ref` / `Fn::GetAtt` / `Fn::Sub` placeholders in\n * the function's `Properties.Environment.Variables` with the deployed\n * values. The result feeds back into the existing\n * `resolveEnvVars(...)` pipeline so `--env-vars` overrides still take\n * precedence — that ordering matters because users routinely override a\n * single variable while using `--from-state` to recover the rest.\n *\n * Scope:\n *\n * - `Ref: <LogicalId>` — substituted with `state.resources[id].physicalId`.\n * - `Fn::GetAtt: [<LogicalId>, <attr>]` (and the `\"LogicalId.attr\"`\n * string form) — substituted with\n * `state.resources[id].attributes[attr]`. We deliberately do NOT\n * synthesize attributes the provider would normally compute (e.g.\n * IAM role ARNs derived from physicalId + accountId) — `--from-state`\n * surfaces only what cdk-local recorded at deploy time, which is what the\n * deployed Lambda's env actually saw.\n * - `Fn::Sub: '<template>'` (and the two-argument `[template, vars]`\n * form) — `${LogicalId}` / `${LogicalId.attr}` placeholders are\n * substituted in place; pseudo parameters (`${AWS::AccountId}` /\n * `${AWS::Region}` / `${AWS::Partition}` / `${AWS::URLSuffix}`) are\n * substituted from the optional `pseudoParameters` bag; unrelated\n * placeholders (mapping references, parameters) are left untouched\n * and the value is treated as unresolved.\n * - `Fn::Join: [<delimiter>, [<elements>...]]` — every element is\n * recursively resolved via this same module and joined with the\n * delimiter. Closes Gap 1 of issue #286 (the SSM Parameter\n * `ecs.Secret.fromSsmParameter` shape CDK synthesizes is\n * `Fn::Join` with pseudo-parameter `Ref`s + a `Ref` to the\n * parameter; without Fn::Join support the secret silently drops).\n * - `Fn::Select: [<index>, <list>]` — picks the indexed element of an\n * array. Index may be a number or a numeric string (CFn templates\n * often carry the string form). The list argument may be a literal\n * array of intrinsics OR an intrinsic that resolves to a\n * `string[]` (today: only `Fn::Split`). Out-of-bounds / negative /\n * non-finite index reports unresolved. Closes issue #636 — the\n * `Fn.select(N, Fn.split(':', secret.secretArn))` shape CDK\n * synthesizes for parsing secret ARN segments.\n * - `Fn::Split: [<delimiter>, <string>]` — splits a string into a\n * `string[]`. **Only valid INSIDE `Fn::Select`** — at the env-var\n * top level the result is an array which can't be an env-var\n * value, so the top-level dispatcher reports unresolved with a\n * clear reason.\n * - `Ref: AWS::AccountId` / `AWS::Region` / `AWS::Partition` /\n * `AWS::URLSuffix` — substituted from the optional\n * `pseudoParameters` bag the caller supplies. When the bag is\n * missing (or the specific key isn't set), the placeholder reports\n * unresolved — same warn-and-drop policy as every other miss.\n *\n * Cross-stack intrinsics (when the caller supplies a `crossStackResolver`\n * on the `SubstitutionContext` via the async API):\n *\n * - `Fn::ImportValue: '<exportName>'` (or an intrinsic-valued argument\n * that resolves to a string against state + pseudo parameters) —\n * looked up via `crossStackResolver.resolveImport(exportName)`. The\n * resolver typically reads the host's persistent exports index,\n * falling back to a per-stack state.json scan on index miss\n * (closes issue #454).\n * - `Fn::GetStackOutput: { StackName, OutputName, Region? }` — looked\n * up via `crossStackResolver.resolveGetStackOutput(stackName, region,\n * outputName)`. The resolver typically reads the producer stack's\n * state.json from S3 directly. `Region` defaults to the consumer's\n * deploy region when omitted.\n *\n * Both resolvers return `string | undefined`; an `undefined` value\n * reports unresolved per the standard warn-and-drop policy. Cross-account\n * `Fn::GetStackOutput.RoleArn` is rejected at the resolver layer (cdk-local\n * uses S3 state, not CloudFormation; cross-account would require\n * assuming the role and reading the producer account's separate state\n * bucket — tracked under #449).\n *\n * Only the async API (`substituteAgainstStateAsync` /\n * `substituteEnvVarsFromStateAsync`) handles these. The legacy sync API\n * surfaces them as `unresolved` (\"unsupported intrinsic\") so existing\n * callers (e.g. `ecs-task-resolver.ts`'s sync container parsing) stay\n * unchanged and the per-key warn-and-drop UX still fires.\n *\n * Out of scope (deferred):\n *\n * - Cross-region `Fn::ImportValue` (tracked under #451).\n * - Cross-account `Fn::GetStackOutput.RoleArn` (tracked under #449).\n * - Other intrinsics (`Fn::If`, `Fn::FindInMap`, etc.). Anything\n * beyond the supported set is reported as unresolved and the env\n * var is dropped, matching PR 1's \"warn-and-drop\" semantics.\n *\n * Failure mode: per-key best-effort. When a substitution can't be\n * produced (state missing for the referenced logical ID, attribute not\n * captured at deploy time, unsupported intrinsic in `Fn::Sub`), the key\n * is reported as unresolved and the caller drops it from the env block\n * with a warn. We never throw out of substitution — a bad reference in\n * one env var must not abort the whole `cdkl invoke` call.\n */\n\nimport type { ResourceState } from '../types/state.js';\n\n/**\n * Result of substituting a single env-var value against state.\n *\n * The discriminated union is load-bearing: callers decide whether to\n * pass the substituted value into the regular env-resolver pipeline\n * (which then accepts it as a literal) or to drop the key with a warn.\n */\nexport type StateSubstitutionResult =\n | { kind: 'literal'; value: string | number | boolean }\n | { kind: 'unresolved'; reason: string };\n\n/**\n * AWS pseudo parameters supplied by the caller. When set, `Ref: AWS::*`\n * and `${AWS::*}` placeholders inside `Fn::Sub` / `Fn::Join` bodies are\n * substituted from this bag. The CLI layer typically derives every\n * field from the resolved region + an `sts:GetCallerIdentity` call\n * (see `derivePartitionAndUrlSuffix` in `ecs-task-resolver.ts`).\n *\n * Every key is optional; a missing key reports unresolved per the\n * standard warn-and-drop policy.\n */\nexport interface PseudoParameters {\n accountId?: string;\n region?: string;\n partition?: string;\n urlSuffix?: string;\n}\n\n/**\n * Cross-stack lookups consulted by the async substitution path when\n * `Fn::ImportValue` / `Fn::GetStackOutput` are encountered. The async API\n * awaits these — the legacy sync API ignores the field entirely and\n * surfaces both intrinsics as `unresolved`.\n *\n * Both methods return `string | undefined`:\n * - `string` — the resolved value is substituted into the env-var.\n * - `undefined` — the per-key warn-and-drop path fires (e.g. the\n * producer stack has not been deployed yet, the export name has no\n * index entry, etc.).\n *\n * Implementations are expected to be best-effort and never throw the\n * caller's invoke off the rails. A genuine AWS failure (missing\n * credentials, S3 access denied) is best surfaced as `undefined` so the\n * dropped env-var carries a clear per-key reason instead of aborting the\n * whole substitution pass.\n */\nexport interface CrossStackResolver {\n /**\n * Look an export by name against the consumer's region. Implementations\n * typically consult the host's persistent exports index, falling back\n * to a per-stack `state.json` scan on miss.\n */\n resolveImport(exportName: string): Promise<string | undefined>;\n /**\n * Look an output by `(producerStack, producerRegion, outputName)`. The\n * resolver is the canonical \"explicit producer\" path — no Export\n * declaration on the producer side is required. `producerRegion`\n * defaults to the consumer's deploy region when the caller did not\n * supply `Region` on the intrinsic.\n */\n resolveGetStackOutput(\n producerStack: string,\n producerRegion: string,\n outputName: string\n ): Promise<string | undefined>;\n}\n\nexport interface SubstitutionContext {\n /** State-recorded resources for `Ref` / `Fn::GetAtt` / `${LogicalId}` lookups. */\n resources: Record<string, ResourceState>;\n /**\n * Resolved CloudFormation template `Parameters`, keyed by parameter\n * logical ID. Consulted by `Ref` (and `${LogicalId}` inside `Fn::Sub`)\n * when the logical ID is not a resource — covers\n * `AWS::SSM::Parameter::Value<String>` parameters (what CDK synthesizes\n * for `ssm.StringParameter.valueForStringParameter(...)`), which are\n * CloudFormation PARAMETERS, not resources, so they never appear in\n * `resources` (built from `ListStackResources`). The CLI layer resolves\n * these values out-of-band via SSM Parameter Store (see\n * `ssm-parameter-resolver.ts`) and threads the map in here. Optional;\n * absent when no SSM-backed parameter was resolved.\n */\n parameters?: Record<string, string>;\n /**\n * Logical IDs of `parameters` entries whose value is sensitive — today\n * the decrypted `SecureString` SSM parameters resolved under\n * `--from-cfn-stack` (issue #99). When a `Ref` resolves to one of these,\n * {@link onSensitiveParameterConsumed} fires so the caller can route the\n * consuming env key through docker's value-from-process-env form and keep\n * the secret off the `docker run` argv. Optional; absent when no\n * sensitive parameter was resolved.\n */\n sensitiveParameters?: ReadonlySet<string>;\n /**\n * Called whenever a `Ref` resolves to a logical ID listed in\n * {@link sensitiveParameters} — including refs buried inside `Fn::Join` /\n * `Fn::Sub` (the combinators recurse through the same context, so the\n * composed value that embeds the secret triggers it too). The env-var\n * helpers install a per-key sink so they can mark which output keys\n * consumed a sensitive parameter. Optional; ignored when unset.\n */\n onSensitiveParameterConsumed?: (logicalId: string) => void;\n /** Optional pseudo-parameter bag for AWS::* placeholders. */\n pseudoParameters?: PseudoParameters;\n /**\n * Optional cross-stack resolver consumed by the async substitution\n * path for `Fn::ImportValue` / `Fn::GetStackOutput`. When unset (or\n * when the sync API is used), both intrinsics surface as `unresolved`\n * with the standard warn-and-drop semantics.\n */\n crossStackResolver?: CrossStackResolver;\n /**\n * Consumer's own deploy region. Used as the default for\n * `Fn::GetStackOutput.Region` when the intrinsic omits it. The async\n * path falls back to looking the region up off `pseudoParameters.region`\n * when this field is absent.\n */\n consumerRegion?: string;\n}\n\n/**\n * Substitute a single env-var / secret-ValueFrom value (which may be a\n * CFn intrinsic) against the provided state-recorded resources map and\n * optional pseudo-parameter bag.\n *\n * Pure / synchronous / no AWS calls. The caller fetches state via\n * `S3StateBackend.getState(...)` once and (when needed) calls\n * `sts:GetCallerIdentity` once for the `accountId`, then feeds both into\n * each intrinsic substitution.\n *\n * Backward compatible: callers may pass `resources` directly (the\n * pre-PR shape) and the helper will assume `pseudoParameters` is\n * unset — matching the `cdkl invoke --from-state` v1 contract.\n */\nexport function substituteAgainstState(\n value: unknown,\n contextOrResources: SubstitutionContext | Record<string, ResourceState>\n): StateSubstitutionResult {\n const context: SubstitutionContext = isContext(contextOrResources)\n ? contextOrResources\n : { resources: contextOrResources };\n\n // Primitives are already literal — nothing to substitute. The caller\n // (`mergeFromStateIntoTemplateEnv`) generally won't reach this path\n // because the env-resolver already keeps literals untouched, but we\n // accept it here so callers can treat the function uniformly.\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return { kind: 'literal', value };\n }\n\n if (value === null || typeof value !== 'object') {\n return {\n kind: 'unresolved',\n reason: `unsupported value type: ${value === null ? 'null' : typeof value}`,\n };\n }\n\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length !== 1) {\n return {\n kind: 'unresolved',\n reason: `expected an intrinsic with one key, got ${keys.length} keys`,\n };\n }\n\n const intrinsic = keys[0]!;\n const arg = obj[intrinsic];\n\n if (intrinsic === 'Ref') {\n return resolveRef(arg, context);\n }\n if (intrinsic === 'Fn::GetAtt') {\n return resolveGetAtt(arg, context);\n }\n if (intrinsic === 'Fn::Sub') {\n return resolveSub(arg, context);\n }\n if (intrinsic === 'Fn::Join') {\n return resolveJoin(arg, context);\n }\n if (intrinsic === 'Fn::Select') {\n return resolveSelect(arg, context);\n }\n if (intrinsic === 'Fn::Split') {\n // `Fn::Split` returns a string[]; an array cannot be an env-var\n // value, so at the top level we always reject it with a clear\n // reason. It's still resolvable inside `Fn::Select` via\n // `resolveAny` below.\n return {\n kind: 'unresolved',\n reason: `Fn::Split returns an array, which is not a valid env-var value (use Fn::Select to pick one element)`,\n };\n }\n\n return {\n kind: 'unresolved',\n reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join, Fn::Select, Fn::Split)`,\n };\n}\n\nfunction isContext(\n v: SubstitutionContext | Record<string, ResourceState>\n): v is SubstitutionContext {\n // SubstitutionContext requires a `resources` key whose value is itself\n // an object; a bare `Record<string, ResourceState>` has logical-ID keys\n // whose values are ResourceState objects (with `physicalId` etc.). The\n // discriminator: if the value has a `resources` field that is itself an\n // object AND lacks the ResourceState-shaped fields, it's a context.\n if (typeof v !== 'object' || v === null) return false;\n const r = (v as Record<string, unknown>)['resources'];\n if (r === undefined) return false;\n if (typeof r !== 'object' || r === null) return false;\n // A ResourceState has `physicalId` + `resourceType` at the top level;\n // a SubstitutionContext's `resources` is a record of ResourceStates,\n // so the value under `resources` typically lacks `physicalId` itself.\n return !('physicalId' in r);\n}\n\nfunction resolveRef(arg: unknown, context: SubstitutionContext): StateSubstitutionResult {\n if (typeof arg !== 'string' || arg.length === 0) {\n return { kind: 'unresolved', reason: `Ref expects a non-empty logical ID, got ${typeof arg}` };\n }\n if (arg.startsWith('AWS::')) {\n return resolvePseudoParameter(arg, context.pseudoParameters);\n }\n const resource = context.resources[arg];\n if (resource) {\n return { kind: 'literal', value: resource.physicalId };\n }\n // Not a resource — try the resolved CloudFormation parameters map. This\n // covers `AWS::SSM::Parameter::Value<String>` parameters (CDK's\n // `ssm.StringParameter.valueForStringParameter(...)` shape), which are\n // CloudFormation PARAMETERS, not resources, and so never appear in\n // `resources` (built from `ListStackResources`). The CLI resolves their\n // values via SSM and feeds them in on `context.parameters`.\n const parameterValue = context.parameters?.[arg];\n if (parameterValue !== undefined) {\n // Flag sensitive (SecureString) parameter consumption so the caller\n // keeps the consuming env key off the `docker run` argv (issue #99).\n // Fires for refs nested in Fn::Join / Fn::Sub too — the combinators\n // recurse through this same resolveRef with the same context.\n if (context.sensitiveParameters?.has(arg)) {\n context.onSensitiveParameterConsumed?.(arg);\n }\n return { kind: 'literal', value: parameterValue };\n }\n return {\n kind: 'unresolved',\n reason:\n `Ref '${arg}': no record in the state source ` +\n `(was the resource deployed, or is it a CloudFormation parameter that could not be resolved — ` +\n `e.g. an SSM-backed AWS::SSM::Parameter::Value parameter whose SSM value was not readable?)`,\n };\n}\n\nfunction resolvePseudoParameter(\n name: string,\n pseudo: PseudoParameters | undefined\n): StateSubstitutionResult {\n if (!pseudo) {\n return {\n kind: 'unresolved',\n reason: `Ref '${name}': pseudo parameter not supplied (need an active state source, e.g. --from-cfn-stack)`,\n };\n }\n switch (name) {\n case 'AWS::AccountId':\n if (pseudo.accountId !== undefined) return { kind: 'literal', value: pseudo.accountId };\n break;\n case 'AWS::Region':\n if (pseudo.region !== undefined) return { kind: 'literal', value: pseudo.region };\n break;\n case 'AWS::Partition':\n if (pseudo.partition !== undefined) return { kind: 'literal', value: pseudo.partition };\n break;\n case 'AWS::URLSuffix':\n if (pseudo.urlSuffix !== undefined) return { kind: 'literal', value: pseudo.urlSuffix };\n break;\n default:\n return {\n kind: 'unresolved',\n reason: `Ref '${name}': pseudo parameter not supported (supported: AWS::AccountId, AWS::Region, AWS::Partition, AWS::URLSuffix)`,\n };\n }\n return { kind: 'unresolved', reason: `Ref '${name}': pseudo parameter value not resolved` };\n}\n\nfunction resolveGetAtt(arg: unknown, context: SubstitutionContext): StateSubstitutionResult {\n let logicalId: string;\n let attr: string;\n if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === 'string') {\n logicalId = arg[0];\n if (typeof arg[1] !== 'string') {\n return {\n kind: 'unresolved',\n reason: `Fn::GetAtt's second arg must be a string attribute name, got ${typeof arg[1]} (nested intrinsics in attribute names are not supported)`,\n };\n }\n attr = arg[1];\n } else if (typeof arg === 'string') {\n const dot = arg.indexOf('.');\n if (dot <= 0 || dot === arg.length - 1) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetAtt string form must be '<LogicalId>.<Attribute>', got '${arg}'`,\n };\n }\n logicalId = arg.slice(0, dot);\n attr = arg.slice(dot + 1);\n } else {\n return {\n kind: 'unresolved',\n reason: `Fn::GetAtt expects [LogicalId, Attribute] or 'LogicalId.Attribute', got ${\n Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg\n }`,\n };\n }\n\n const resource = context.resources[logicalId];\n if (!resource) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetAtt '${logicalId}.${attr}': no record in the state source`,\n };\n }\n const cached = resource.attributes?.[attr];\n if (cached === undefined) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetAtt '${logicalId}.${attr}': attribute not captured in the state source at deploy time`,\n };\n }\n if (typeof cached === 'string' || typeof cached === 'number' || typeof cached === 'boolean') {\n return { kind: 'literal', value: cached };\n }\n // Object/array attribute values (e.g. `S3CanonicalUserId` is a string,\n // but `Endpoints` on some resources is an object). Lambda env vars\n // have to be flat strings, so we surface them as JSON — same posture\n // CFn / SAM take, and lets the handler re-parse if it knows the shape.\n return { kind: 'literal', value: JSON.stringify(cached) };\n}\n\n/**\n * `Fn::Sub` accepts:\n * - `'a-${LogicalId}-b'` — single-string form, placeholders against\n * the template / state / pseudo parameters.\n * - `['a-${X}-b', { X: <intrinsic-or-literal> }]` — two-arg form, the\n * map provides override values for placeholders that aren't logical\n * IDs. We recursively resolve each map value via this same module\n * so a placeholder bound to `{Ref: ...}` works.\n */\nfunction resolveSub(arg: unknown, context: SubstitutionContext): StateSubstitutionResult {\n let template: string;\n let bindings: Record<string, unknown> = {};\n\n if (typeof arg === 'string') {\n template = arg;\n } else if (\n Array.isArray(arg) &&\n arg.length === 2 &&\n typeof arg[0] === 'string' &&\n arg[1] !== null &&\n typeof arg[1] === 'object' &&\n !Array.isArray(arg[1])\n ) {\n template = arg[0];\n bindings = arg[1] as Record<string, unknown>;\n } else {\n return {\n kind: 'unresolved',\n reason: `Fn::Sub expects a string or [string, object], got ${\n Array.isArray(arg) ? 'malformed array' : typeof arg\n }`,\n };\n }\n\n // Walk the template and substitute every `${...}` placeholder. We treat\n // any failure mid-walk as a failure for the whole value — partial\n // substitutions would silently produce wrong env vars.\n const placeholderRegex = /\\$\\{([^}]+)\\}/g;\n const placeholders: string[] = [];\n template.replace(placeholderRegex, (_, key: string) => {\n placeholders.push(key);\n return '';\n });\n\n // Eagerly resolve every placeholder so we can fail fast with a clean\n // reason instead of leaving a half-rewritten string.\n const resolutions = new Map<string, string>();\n for (const placeholder of placeholders) {\n if (resolutions.has(placeholder)) continue;\n\n if (placeholder in bindings) {\n const sub = substituteAgainstState(bindings[placeholder], context);\n if (sub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::Sub placeholder '\\${${placeholder}}': ${sub.reason}`,\n };\n }\n resolutions.set(placeholder, String(sub.value));\n continue;\n }\n\n // Pseudo parameter (`${AWS::AccountId}` etc.)\n if (placeholder.startsWith('AWS::')) {\n const sub = resolvePseudoParameter(placeholder, context.pseudoParameters);\n if (sub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::Sub placeholder '\\${${placeholder}}': ${sub.reason}`,\n };\n }\n resolutions.set(placeholder, String(sub.value));\n continue;\n }\n\n // Not in bindings, not a pseudo → treat as a `${LogicalId}` or `${LogicalId.attr}`.\n const dot = placeholder.indexOf('.');\n if (dot === -1) {\n const sub = resolveRef(placeholder, context);\n if (sub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::Sub placeholder '\\${${placeholder}}': ${sub.reason}`,\n };\n }\n resolutions.set(placeholder, String(sub.value));\n } else {\n const sub = resolveGetAtt(placeholder, context);\n if (sub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::Sub placeholder '\\${${placeholder}}': ${sub.reason}`,\n };\n }\n resolutions.set(placeholder, String(sub.value));\n }\n }\n\n const out = template.replace(placeholderRegex, (_, key: string) => {\n return resolutions.get(key) ?? '';\n });\n return { kind: 'literal', value: out };\n}\n\n/**\n * `Fn::Join: [<delimiter>, [<elements>]]` — recursively resolve every\n * element through `substituteAgainstState` and join with the delimiter.\n * Closes the SSM Parameter `ecs.Secret.fromSsmParameter` shape (Gap 1\n * of #286) where CDK synthesizes a `Fn::Join` over pseudo-parameter\n * `Ref`s + a `Ref` to the parameter.\n *\n * String / number / boolean elements pass through as-is; intrinsic\n * elements (`Ref` / `Fn::GetAtt` / nested `Fn::Sub` / nested `Fn::Join`)\n * recurse. Any unresolvable element fails the whole join — partial\n * substitutions would silently produce wrong values.\n */\nfunction resolveJoin(arg: unknown, context: SubstitutionContext): StateSubstitutionResult {\n if (!Array.isArray(arg) || arg.length !== 2 || !Array.isArray(arg[1])) {\n return {\n kind: 'unresolved',\n reason: `Fn::Join expects [delimiter, [elements]], got ${\n Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg\n }`,\n };\n }\n const [delimiterRaw, elements] = arg as [unknown, unknown[]];\n if (typeof delimiterRaw !== 'string') {\n return {\n kind: 'unresolved',\n reason: `Fn::Join delimiter must be a string, got ${typeof delimiterRaw}`,\n };\n }\n\n // Each element MUST resolve to a scalar — `Fn::Join` of arrays is not\n // a thing in CFn. We deliberately call `substituteAgainstState` (the\n // scalar-only top-level dispatcher) rather than `resolveAny` so that a\n // bare `Fn::Split` element gets the same warn-and-drop UX as at the\n // top level. (Inside `Fn::Select`, `resolveAny` IS used to admit\n // arrays — that asymmetry mirrors `intrinsic-image.ts`.)\n const parts: string[] = [];\n for (let i = 0; i < elements.length; i += 1) {\n const sub = substituteAgainstState(elements[i], context);\n if (sub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::Join element [${i}]: ${sub.reason}`,\n };\n }\n parts.push(String(sub.value));\n }\n return { kind: 'literal', value: parts.join(delimiterRaw) };\n}\n\n/**\n * `Fn::Select: [<index>, <list>]` — pick the indexed element of an\n * array. Mirrors the semantics of `resolveImageIntrinsic`'s `Fn::Select`\n * branch in `src/local/intrinsic-image.ts` so the two resolvers behave\n * the same way for the same template shape.\n *\n * - Index may be a number (`0`) OR a numeric string (`'0'`) — CFn\n * templates often carry the string form after a JSON round-trip.\n * - List may be a resolved-array intrinsic (today only `Fn::Split`\n * produces a `string[]`) OR a literal `[...]` array of intrinsics\n * resolved on the fly.\n * - Out-of-bounds / negative / non-finite index reports unresolved.\n */\nfunction resolveSelect(arg: unknown, context: SubstitutionContext): StateSubstitutionResult {\n if (!Array.isArray(arg) || arg.length !== 2) {\n return {\n kind: 'unresolved',\n reason: `Fn::Select expects [index, list], got ${\n Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg\n }`,\n };\n }\n const [rawIndex, listArg] = arg as [unknown, unknown];\n\n let index: number | undefined;\n if (typeof rawIndex === 'number') {\n index = rawIndex;\n } else if (typeof rawIndex === 'string' && /^-?\\d+$/.test(rawIndex)) {\n index = Number.parseInt(rawIndex, 10);\n }\n if (index === undefined || !Number.isFinite(index)) {\n return {\n kind: 'unresolved',\n reason: `Fn::Select index must be a finite number (or numeric string), got ${typeof rawIndex}`,\n };\n }\n if (index < 0) {\n return {\n kind: 'unresolved',\n reason: `Fn::Select index must be non-negative, got ${index}`,\n };\n }\n\n // Resolve the list arg through the array-tolerant helper so a nested\n // `Fn::Split` produces a real `string[]`.\n const list = resolveAny(listArg, context);\n if (list.kind === 'unresolved') {\n return {\n kind: 'unresolved',\n reason: `Fn::Select list: ${list.reason}`,\n };\n }\n\n if (Array.isArray(list.value)) {\n if (index >= list.value.length) {\n return {\n kind: 'unresolved',\n reason: `Fn::Select index ${index} out of bounds (list length ${list.value.length})`,\n };\n }\n const picked = list.value[index]!;\n return { kind: 'literal', value: picked };\n }\n\n // The list arg resolved to a scalar — that's not a valid list. Most\n // common cause: the template author passed a single intrinsic that\n // resolves to a string instead of a `Fn::Split` / literal array.\n return {\n kind: 'unresolved',\n reason: `Fn::Select list must resolve to an array, got ${typeof list.value}`,\n };\n}\n\n/**\n * `Fn::Split: [<delimiter>, <string>]` — split a string into a\n * `string[]`. Only callable through `resolveAny` (i.e. inside\n * `Fn::Select`); the top-level dispatcher rejects bare `Fn::Split`\n * since an array cannot be an env-var value.\n *\n * The string argument can itself be an intrinsic (typical CDK shape:\n * `Fn::Split: [':', { 'Fn::GetAtt': [<Secret>, 'SecretArn'] }]`); it's\n * resolved through `substituteAgainstState` so we don't accidentally\n * admit nested-array shapes there.\n */\nfunction resolveSplitAsArray(\n arg: unknown,\n context: SubstitutionContext\n): { kind: 'literal'; value: string[] } | { kind: 'unresolved'; reason: string } {\n if (!Array.isArray(arg) || arg.length !== 2) {\n return {\n kind: 'unresolved',\n reason: `Fn::Split expects [delimiter, string], got ${\n Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg\n }`,\n };\n }\n const [delim, src] = arg as [unknown, unknown];\n if (typeof delim !== 'string') {\n return {\n kind: 'unresolved',\n reason: `Fn::Split delimiter must be a string, got ${typeof delim}`,\n };\n }\n const sub = substituteAgainstState(src, context);\n if (sub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::Split string argument: ${sub.reason}`,\n };\n }\n if (typeof sub.value !== 'string') {\n return {\n kind: 'unresolved',\n reason: `Fn::Split string argument must resolve to a string, got ${typeof sub.value}`,\n };\n }\n return { kind: 'literal', value: sub.value.split(delim) };\n}\n\n/**\n * Array-tolerant resolver used by `Fn::Select`'s `list` argument.\n * Returns either the scalar `StateSubstitutionResult` shape (delegating\n * to `substituteAgainstState`) OR a `{kind: 'literal', value: string[]}`\n * when the node is `Fn::Split` / a literal array of intrinsics.\n *\n * Top-level `substituteAgainstState` deliberately doesn't go through\n * this helper — env-var values can't be arrays, and the asymmetry\n * matches `intrinsic-image.ts`'s `resolveImageIntrinsic` (scalar) vs\n * `resolveImageIntrinsicAny` (scalar OR array) split.\n */\nfunction resolveAny(\n value: unknown,\n context: SubstitutionContext\n):\n | { kind: 'literal'; value: string | number | boolean | string[] }\n | { kind: 'unresolved'; reason: string } {\n // Literal arrays of intrinsics: resolve each element to a scalar.\n if (Array.isArray(value)) {\n const out: string[] = [];\n for (let i = 0; i < value.length; i += 1) {\n const sub = substituteAgainstState(value[i], context);\n if (sub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `list element [${i}]: ${sub.reason}`,\n };\n }\n out.push(String(sub.value));\n }\n return { kind: 'literal', value: out };\n }\n\n // Direct `Fn::Split` intrinsic — produces an array.\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value as Record<string, unknown>).length === 1 &&\n Object.prototype.hasOwnProperty.call(value, 'Fn::Split')\n ) {\n return resolveSplitAsArray((value as Record<string, unknown>)['Fn::Split'], context);\n }\n\n // Anything else: fall back to the scalar top-level dispatcher.\n return substituteAgainstState(value, context);\n}\n\n/**\n * Async sibling of {@link substituteAgainstState}. Same semantics for every\n * intrinsic the sync path supports; additionally consults the\n * `crossStackResolver` (when supplied on the context) for `Fn::ImportValue`\n * and `Fn::GetStackOutput`.\n *\n * Callers that don't need cross-stack support should keep using the sync\n * helper. Code paths that wire `--from-state` env / secret substitution\n * (e.g. `cdkl invoke --from-state`, `cdkl run-task --from-state`)\n * route through this async version so a single env-var referencing a\n * cross-stack output is no longer warn-and-dropped.\n */\nexport async function substituteAgainstStateAsync(\n value: unknown,\n contextOrResources: SubstitutionContext | Record<string, ResourceState>\n): Promise<StateSubstitutionResult> {\n const context: SubstitutionContext = isContext(contextOrResources)\n ? contextOrResources\n : { resources: contextOrResources };\n\n // Primitives flow through unchanged — same as the sync path.\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n return { kind: 'literal', value };\n }\n\n if (value === null || typeof value !== 'object') {\n return {\n kind: 'unresolved',\n reason: `unsupported value type: ${value === null ? 'null' : typeof value}`,\n };\n }\n\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj);\n if (keys.length !== 1) {\n return {\n kind: 'unresolved',\n reason: `expected an intrinsic with one key, got ${keys.length} keys`,\n };\n }\n\n const intrinsic = keys[0]!;\n const arg = obj[intrinsic];\n\n // For every intrinsic the sync path supports, the sync helper is fully\n // sufficient — recurse via the sync path so we don't pay the async\n // overhead on every nested element. The sync helper recurses into\n // bindings / Join elements through the same module, so any cross-stack\n // intrinsic nested under (say) a `Fn::Join` is NOT resolvable today.\n // That's an intentional v1 scope limit: cross-stack intrinsics\n // typically appear at the env-var ROOT, not buried inside a join. We\n // can lift the limit later by replacing the sync recursion below with\n // async recursion when there's a real-world need.\n if (\n intrinsic === 'Ref' ||\n intrinsic === 'Fn::GetAtt' ||\n intrinsic === 'Fn::Sub' ||\n intrinsic === 'Fn::Join' ||\n intrinsic === 'Fn::Select' ||\n intrinsic === 'Fn::Split'\n ) {\n return substituteAgainstState(value, context);\n }\n\n if (intrinsic === 'Fn::ImportValue') {\n return resolveImportValueAsync(arg, context);\n }\n if (intrinsic === 'Fn::GetStackOutput') {\n return resolveGetStackOutputAsync(arg, context);\n }\n\n return {\n kind: 'unresolved',\n reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join, Fn::Select, Fn::Split, Fn::ImportValue, Fn::GetStackOutput)`,\n };\n}\n\n/**\n * `Fn::ImportValue: <exportName>` — the argument may itself be an\n * intrinsic that resolves to a string (e.g.\n * `{Fn::ImportValue: {Fn::Sub: 'MyStack-${AWS::Region}-Bucket'}}` — the\n * inner `Fn::Sub` is resolved against `pseudoParameters` first, then the\n * resulting string is looked up via the cross-stack resolver).\n */\nasync function resolveImportValueAsync(\n arg: unknown,\n context: SubstitutionContext\n): Promise<StateSubstitutionResult> {\n // Resolve the inner argument first (a string passes through; an\n // intrinsic is resolved against the sync path, which handles every\n // shape an export-name argument might take in practice). We don't\n // recurse into the async path here because the inner part cannot\n // itself be `Fn::ImportValue` — AWS rejects that nesting too.\n const inner = substituteAgainstState(arg, context);\n if (inner.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::ImportValue argument: ${inner.reason}`,\n };\n }\n if (typeof inner.value !== 'string' || inner.value.length === 0) {\n return {\n kind: 'unresolved',\n reason: `Fn::ImportValue argument must resolve to a non-empty string, got ${typeof inner.value}`,\n };\n }\n const exportName = inner.value;\n\n if (!context.crossStackResolver) {\n return {\n kind: 'unresolved',\n reason: `Fn::ImportValue '${exportName}': no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`,\n };\n }\n\n let resolved: string | undefined;\n try {\n resolved = await context.crossStackResolver.resolveImport(exportName);\n } catch (err) {\n return {\n kind: 'unresolved',\n reason: `Fn::ImportValue '${exportName}': lookup failed: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n if (resolved === undefined) {\n return {\n kind: 'unresolved',\n reason: `Fn::ImportValue '${exportName}': export not found in any deployed stack in this region`,\n };\n }\n return { kind: 'literal', value: resolved };\n}\n\n/**\n * `Fn::GetStackOutput: { StackName, OutputName, Region? }`. Same shape as\n * the deploy-engine resolver. `RoleArn` (cross-account) is intentionally\n * NOT supported in this path — the user-visible error message points at\n * the followup issue tracking it.\n */\nasync function resolveGetStackOutputAsync(\n arg: unknown,\n context: SubstitutionContext\n): Promise<StateSubstitutionResult> {\n if (!arg || typeof arg !== 'object' || Array.isArray(arg)) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput argument must be an object with StackName / OutputName / Region, got ${\n arg === null ? 'null' : Array.isArray(arg) ? 'array' : typeof arg\n }`,\n };\n }\n const args = arg as Record<string, unknown>;\n\n const stackNameSub = substituteAgainstState(args['StackName'], context);\n if (stackNameSub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput.StackName: ${stackNameSub.reason}`,\n };\n }\n if (typeof stackNameSub.value !== 'string' || stackNameSub.value.length === 0) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput.StackName must resolve to a non-empty string, got ${typeof stackNameSub.value}`,\n };\n }\n const stackName = stackNameSub.value;\n\n const outputNameSub = substituteAgainstState(args['OutputName'], context);\n if (outputNameSub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput.OutputName: ${outputNameSub.reason}`,\n };\n }\n if (typeof outputNameSub.value !== 'string' || outputNameSub.value.length === 0) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput.OutputName must resolve to a non-empty string, got ${typeof outputNameSub.value}`,\n };\n }\n const outputName = outputNameSub.value;\n\n let region: string | undefined;\n if (args['Region'] !== undefined && args['Region'] !== null) {\n const regionSub = substituteAgainstState(args['Region'], context);\n if (regionSub.kind !== 'literal') {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput.Region: ${regionSub.reason}`,\n };\n }\n if (typeof regionSub.value !== 'string' || regionSub.value.length === 0) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput.Region must resolve to a non-empty string, got ${typeof regionSub.value}`,\n };\n }\n region = regionSub.value;\n } else {\n region = context.consumerRegion ?? context.pseudoParameters?.region;\n }\n if (!region) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput '${stackName}.${outputName}': no Region supplied and consumer region is unknown (set --region, AWS_REGION, or env.region on the CDK stack)`,\n };\n }\n\n if (args['RoleArn'] !== undefined && args['RoleArn'] !== null) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput '${stackName}.${outputName}': RoleArn (cross-account) is not yet supported by the state source — tracked under issue #449`,\n };\n }\n\n if (!context.crossStackResolver) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput '${stackName}.${outputName}': no cross-stack resolver supplied (pass a state-source flag, e.g. --from-cfn-stack, and ensure the producer stack was deployed)`,\n };\n }\n\n let resolved: string | undefined;\n try {\n resolved = await context.crossStackResolver.resolveGetStackOutput(\n stackName,\n region,\n outputName\n );\n } catch (err) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput '${stackName}.${outputName}' (${region}): lookup failed: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n if (resolved === undefined) {\n return {\n kind: 'unresolved',\n reason: `Fn::GetStackOutput '${stackName}.${outputName}' (${region}): output not found in producer stack state`,\n };\n }\n return { kind: 'literal', value: resolved };\n}\n\n/**\n * High-level helper: walk the function's `Properties.Environment.Variables`\n * map and produce a pre-resolved version where every value is a literal\n * (substituted from state when possible). Values that can't be substituted\n * are removed so the downstream `resolveEnvVars` treats them as missing\n * and the caller's existing intrinsic-warn-and-drop path fires.\n *\n * Returns the substitution audit alongside the rewritten map so the CLI\n * layer can log per-key info (`STATE_BUCKET=bucket-1234` / `TABLE_ARN\n * skipped: state missing`).\n */\nexport interface StateEnvSubstitutionAudit {\n /** Keys whose substitution succeeded — surfaced as literals in `env`. */\n resolvedKeys: string[];\n /**\n * Keys that the substituter could not resolve (state missing,\n * unsupported intrinsic, etc.) — paired with a per-key reason so the\n * CLI layer can warn-and-drop with context.\n */\n unresolved: Array<{ key: string; reason: string }>;\n /**\n * Keys whose resolved value consumed a sensitive (SecureString) parameter\n * (see {@link SubstitutionContext.sensitiveParameters}). The CLI layer\n * routes these through docker's value-from-process-env form so the\n * decrypted secret never lands on the `docker run` argv (issue #99).\n */\n sensitiveKeys: string[];\n}\n\n/**\n * Build a pre-substituted env map from the template entry by feeding each\n * intrinsic value through `substituteAgainstState`. Literal entries pass\n * through untouched (the env-resolver handles them).\n *\n * @param templateEnv The function's `Properties.Environment.Variables`\n * map from the synthesized template, or `undefined`\n * when the function has no env vars.\n * @param resources `state.resources` from the host's S3 state file for\n * the function's stack.\n */\nexport function substituteEnvVarsFromState(\n templateEnv: Record<string, unknown> | undefined,\n contextOrResources: SubstitutionContext | Record<string, ResourceState>\n): { env: Record<string, unknown>; audit: StateEnvSubstitutionAudit } {\n const env: Record<string, unknown> = {};\n const audit: StateEnvSubstitutionAudit = { resolvedKeys: [], unresolved: [], sensitiveKeys: [] };\n\n if (!templateEnv) return { env, audit };\n\n const context: SubstitutionContext = isContext(contextOrResources)\n ? contextOrResources\n : { resources: contextOrResources };\n\n for (const [key, value] of Object.entries(templateEnv)) {\n // Cheap fast path for already-literal values: no substitution\n // attempted, no audit entry — env-resolver will simply keep them.\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n env[key] = value;\n continue;\n }\n\n const { result, consumedSensitive } = resolveWithSensitivity(value, context);\n if (result.kind === 'literal') {\n env[key] = result.value;\n audit.resolvedKeys.push(key);\n if (consumedSensitive) audit.sensitiveKeys.push(key);\n } else {\n audit.unresolved.push({ key, reason: result.reason });\n // Intentionally drop the key — that way the downstream\n // env-resolver sees \"no template value for K\" and the existing\n // PR 1 warn-and-drop fires with the same UX.\n }\n }\n\n return { env, audit };\n}\n\n/**\n * Resolve a single intrinsic value while detecting whether its resolution\n * consumed a sensitive (SecureString) parameter — including refs nested in\n * `Fn::Join` / `Fn::Sub`, since the combinators recurse through the same\n * per-key context. Installs a per-call sink on a shallow context copy so\n * the shared input context is never mutated. Used by both the sync and\n * async env-var helpers (the async path delegates these intrinsics to the\n * sync resolver, so the sink fires identically).\n */\nexport function resolveWithSensitivity(\n value: unknown,\n context: SubstitutionContext\n): { result: StateSubstitutionResult; consumedSensitive: boolean } {\n if (!context.sensitiveParameters || context.sensitiveParameters.size === 0) {\n return { result: substituteAgainstState(value, context), consumedSensitive: false };\n }\n let consumedSensitive = false;\n const keyContext: SubstitutionContext = {\n ...context,\n onSensitiveParameterConsumed: () => {\n consumedSensitive = true;\n },\n };\n return { result: substituteAgainstState(value, keyContext), consumedSensitive };\n}\n\n/**\n * Async sibling of {@link resolveWithSensitivity}. Routes through\n * {@link substituteAgainstStateAsync} (for `Fn::ImportValue` /\n * `Fn::GetStackOutput`), which delegates SSM-parameter-bearing intrinsics\n * to the sync resolver — so the sensitivity sink fires the same way.\n */\nexport async function resolveWithSensitivityAsync(\n value: unknown,\n context: SubstitutionContext\n): Promise<{ result: StateSubstitutionResult; consumedSensitive: boolean }> {\n if (!context.sensitiveParameters || context.sensitiveParameters.size === 0) {\n return { result: await substituteAgainstStateAsync(value, context), consumedSensitive: false };\n }\n let consumedSensitive = false;\n const keyContext: SubstitutionContext = {\n ...context,\n onSensitiveParameterConsumed: () => {\n consumedSensitive = true;\n },\n };\n return { result: await substituteAgainstStateAsync(value, keyContext), consumedSensitive };\n}\n\n/**\n * Async sibling of {@link substituteEnvVarsFromState}. Routes every\n * intrinsic-valued entry through {@link substituteAgainstStateAsync} so\n * `Fn::ImportValue` / `Fn::GetStackOutput` resolve via the context's\n * `crossStackResolver` (when supplied). Mirrors the sync version in every\n * other respect: literal entries pass through unchanged, unresolved\n * entries are dropped with a per-key audit reason, and the env-resolver\n * sees the same \"no template value\" shape so the warn-and-drop path\n * fires consistently.\n *\n * Closes issue #454 — `cdkl invoke --from-state` and\n * `cdkl run-task --from-state` can now resolve cross-stack output\n * references in env vars / secrets instead of warn-and-dropping them.\n */\nexport async function substituteEnvVarsFromStateAsync(\n templateEnv: Record<string, unknown> | undefined,\n contextOrResources: SubstitutionContext | Record<string, ResourceState>\n): Promise<{ env: Record<string, unknown>; audit: StateEnvSubstitutionAudit }> {\n const env: Record<string, unknown> = {};\n const audit: StateEnvSubstitutionAudit = { resolvedKeys: [], unresolved: [], sensitiveKeys: [] };\n\n if (!templateEnv) return { env, audit };\n\n const context: SubstitutionContext = isContext(contextOrResources)\n ? contextOrResources\n : { resources: contextOrResources };\n\n for (const [key, value] of Object.entries(templateEnv)) {\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n env[key] = value;\n continue;\n }\n\n const { result, consumedSensitive } = await resolveWithSensitivityAsync(value, context);\n if (result.kind === 'literal') {\n env[key] = result.value;\n audit.resolvedKeys.push(key);\n if (consumedSensitive) audit.sensitiveKeys.push(key);\n } else {\n audit.unresolved.push({ key, reason: result.reason });\n }\n }\n\n return { env, audit };\n}\n\n/**\n * Last-resort fill for env keys that static substitution could not\n * resolve, using the consumer resource's already-deployed environment.\n *\n * When a Lambda / container is bound to a deployed CloudFormation stack\n * (`--from-cfn-stack`), the consumer itself is deployed, so CloudFormation\n * already resolved every intrinsic (`Fn::GetAtt`, `Fn::Sub`,\n * `Fn::ImportValue`, cross-stack `Ref`) into its concrete deployed env\n * value. `ListStackResources` does not return per-attribute values, so\n * the static substituter drops those keys — but the deployed env carries\n * the answer. The provider fetches the deployed env (e.g.\n * `lambda:GetFunctionConfiguration`) and this helper splices it in for\n * the keys that remained unresolved.\n *\n * Precedence: only keys in `unresolved` are touched — statically resolved\n * keys and literal keys are left exactly as the substituter produced\n * them, and a `--env-vars` override still wins downstream. Keys absent\n * from `deployedEnv` stay unresolved so the caller's warn-and-drop fires.\n *\n * Pure: returns new arrays / a new env map, never mutates the inputs.\n */\nexport function applyDeployedEnvFallback(\n resolvedEnv: Record<string, unknown>,\n unresolved: ReadonlyArray<{ key: string; reason: string }>,\n deployedEnv: Record<string, string> | undefined\n): {\n env: Record<string, unknown>;\n filled: string[];\n stillUnresolved: Array<{ key: string; reason: string }>;\n} {\n if (!deployedEnv) {\n return { env: resolvedEnv, filled: [], stillUnresolved: [...unresolved] };\n }\n const env = { ...resolvedEnv };\n const filled: string[] = [];\n const stillUnresolved: Array<{ key: string; reason: string }> = [];\n for (const item of unresolved) {\n const deployedValue = deployedEnv[item.key];\n if (deployedValue !== undefined) {\n env[item.key] = deployedValue;\n filled.push(item.key);\n } else {\n stillUnresolved.push({ key: item.key, reason: item.reason });\n }\n }\n return { env, filled, stillUnresolved };\n}\n","import { dirname, isAbsolute, resolve } from 'node:path';\nimport { existsSync, statSync } from 'node:fs';\nimport type { StackInfo } from '../synthesis/assembly-reader.js';\nimport type { TemplateResource } from '../types/resource.js';\nimport { buildCdkPathIndex, resolveCdkPathToLogicalIds } from '../cli/cdk-path.js';\nimport { matchStacks } from '../cli/stack-matcher.js';\nimport {\n substituteImagePlaceholders,\n tryResolveImageFnJoin,\n type ImageResolutionContext,\n} from './intrinsic-image.js';\nimport {\n substituteAgainstState,\n substituteAgainstStateAsync,\n resolveWithSensitivity,\n resolveWithSensitivityAsync,\n type SubstitutionContext,\n} from './state-resolver.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Result of resolving a `cdkl run-task <target>` argument back to a\n * concrete `AWS::ECS::TaskDefinition` in the synthesized assembly. The\n * shape mirrors what the ECS API + the CFn template expose so the runner\n * can directly translate each field into `docker run` flags / `docker\n * network` lifecycle calls without re-walking the template.\n *\n * Phase 1: no AWS-side ECS API calls are needed to build this — every\n * field is template-derived. `Secrets[].valueFrom` resolution happens\n * separately in `ecs-secrets-resolver.ts` so this module stays free of\n * AWS SDK imports.\n */\nexport interface ResolvedEcsTask {\n /** Stack the task definition belongs to. */\n stack: StackInfo;\n /** Logical id of the AWS::ECS::TaskDefinition resource. */\n taskDefinitionLogicalId: string;\n /** Raw template entry — kept for future feature additions. */\n resource: TemplateResource;\n /**\n * Task family. Falls back to the logical id when not declared (CDK auto-\n * generates a family in this case but only at deploy time; locally we\n * surface the logical id so logs are still identifiable).\n */\n family: string;\n /**\n * Default `bridge`. `awsvpc` is mapped to `bridge` locally with a warn\n * because docker cannot emulate ENI-per-task; `host` and `none` pass\n * through unchanged.\n */\n networkMode: 'bridge' | 'awsvpc' | 'host' | 'none';\n /**\n * Resolved task role ARN. Surfaced as either:\n * - a flat string ARN passed through verbatim from the template, OR\n * - a synth-time placeholder of the shape\n * `arn:aws:iam::${AWS::AccountId}:role/<RoleLogicalId>` when the\n * `TaskRoleArn` is a `Ref` / `Fn::GetAtt` against an `AWS::IAM::Role`\n * in the same stack. The CLI substitutes the placeholder account\n * segment lazily via STS `GetCallerIdentity` when (and only when)\n * `--assume-task-role` is used in its bare form.\n * - `undefined` when the template's `TaskRoleArn` is missing OR is an\n * intrinsic that doesn't reference a same-stack IAM Role (e.g.\n * points at a non-IAM-Role resource type or is an unsupported\n * intrinsic shape — see `resolveRoleArn`).\n */\n taskRoleArn?: string;\n /** Resolved execution role ARN. Follows the same shape as `taskRoleArn`. */\n executionRoleArn?: string;\n containers: ResolvedEcsContainer[];\n volumes: ResolvedEcsVolume[];\n /**\n * `RuntimePlatform.CpuArchitecture` + `OperatingSystemFamily`. Only\n * `X86_64` / `ARM64` + `LINUX` are routed in v1; other values pass\n * through unchanged but the docker-runner only honors the arch field.\n */\n runtimePlatform?: { cpuArchitecture: 'X86_64' | 'ARM64'; operatingSystemFamily: 'LINUX' };\n /**\n * Resolution warnings (e.g. `awsvpc` → `bridge` map) the caller may\n * want to surface at startup. Non-fatal — the runner still proceeds.\n */\n warnings: string[];\n}\n\nexport interface ResolvedEcsContainer {\n name: string;\n image: ResolvedEcsImage;\n command?: string[];\n entryPoint?: string[];\n workingDirectory?: string;\n /** Literal-only env vars; intrinsic-valued entries are dropped (matches `cdkl invoke` v1). */\n environment: Record<string, string>;\n /**\n * Env keys whose value resolved to a decrypted `SecureString` SSM\n * parameter (issue #99). The runner unions these into the\n * value-from-process-env set so the secret never lands on the\n * `docker run` argv.\n */\n sensitiveEnvKeys: string[];\n /** SecretArn entries. Resolved to real values by `ecs-secrets-resolver.ts`. */\n secrets: { name: string; valueFrom: string }[];\n /**\n * Container port mappings. `name` (mirrors the CFn `Name` field) is\n * surfaced for Service Connect resolution (Phase 3 of #262 / Issue\n * #460) — the resolver matches `ServiceConnectConfiguration.Services[].PortName`\n * against this field.\n */\n portMappings: {\n name?: string;\n containerPort: number;\n hostPort?: number;\n protocol: 'tcp' | 'udp';\n }[];\n mountPoints: { sourceVolume: string; containerPath: string; readOnly: boolean }[];\n dependsOn: { containerName: string; condition: 'START' | 'COMPLETE' | 'SUCCESS' | 'HEALTHY' }[];\n links: string[];\n /** Default true per AWS docs — when not set, the container is treated as essential. */\n essential: boolean;\n healthCheck?: {\n command: string[];\n interval?: number;\n timeout?: number;\n retries?: number;\n startPeriod?: number;\n };\n user?: string;\n privileged?: boolean;\n readonlyRootFilesystem?: boolean;\n ulimits: { name: string; softLimit: number; hardLimit: number }[];\n /**\n * Non-fatal warnings produced while parsing this container — typically\n * intrinsic-valued env vars or secret ValueFrom entries that could not\n * be substituted against state. The CLI prints these so the user\n * understands why an expected env / secret is missing.\n */\n warnings: string[];\n}\n\nexport type ResolvedEcsImage =\n | { kind: 'cdk-asset'; assetHash?: string }\n | { kind: 'ecr'; uri: string; account: string; region: string }\n | { kind: 'public'; uri: string };\n\nexport interface ResolvedEcsVolume {\n name: string;\n kind: 'host' | 'docker';\n /** Absolute host path for a bind mount. Undefined → docker anonymous volume. */\n hostPath?: string;\n /**\n * `DockerVolumeConfiguration`. When set, the volume is realized via\n * `docker volume create` rather than a bind mount.\n */\n dockerVolumeConfig?: {\n scope: 'task' | 'shared';\n autoprovision?: boolean;\n driver?: string;\n driverOpts?: Record<string, string>;\n labels?: Record<string, string>;\n };\n}\n\nexport class EcsTaskResolutionError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'EcsTaskResolutionError';\n Object.setPrototypeOf(this, EcsTaskResolutionError.prototype);\n }\n}\n\n/**\n * Derive the AWS partition / URL suffix for an AWS region. Same mapping\n * CloudFormation applies to `${AWS::Partition}` / `${AWS::URLSuffix}`.\n * Exported so the CLI can keep the STS hop minimal — caller passes the\n * region in once, this returns the matching partition + suffix.\n */\nexport function derivePartitionAndUrlSuffix(region: string): {\n partition: string;\n urlSuffix: string;\n} {\n if (region.startsWith('cn-')) return { partition: 'aws-cn', urlSuffix: 'amazonaws.com.cn' };\n if (region.startsWith('us-gov-')) return { partition: 'aws-us-gov', urlSuffix: 'amazonaws.com' };\n if (region.startsWith('us-iso-')) return { partition: 'aws-iso', urlSuffix: 'c2s.ic.gov' };\n if (region.startsWith('us-isob-')) return { partition: 'aws-iso-b', urlSuffix: 'sc2s.sgov.gov' };\n return { partition: 'aws', urlSuffix: 'amazonaws.com' };\n}\n\n/**\n * Optional substitution data fed into `parseContainerImage`. Closes issue\n * #264 — container `Image` fields shaped as `Fn::Sub` against AWS pseudo\n * parameters (`${AWS::AccountId}` / `${AWS::Region}` / `${AWS::Partition}` /\n * `${AWS::URLSuffix}`) and / or same-stack `AWS::ECR::Repository` refs are\n * resolvable at runtime when the caller can supply the account ID + region\n * (Tier 1, no state needed) and optionally the host state-recorded\n * `physicalId` map (Tier 2, `--from-state`).\n *\n * The CLI command resolves both blocks lazily — STS is only invoked when\n * at least one container's `Image` references the pseudo parameters — and\n * passes the resolved shape here. The resolver itself stays pure and\n * synchronous.\n */\n/**\n * Substitution context for ECS resolution. Re-exported alias for the\n * shared `ImageResolutionContext` in `intrinsic-image.ts` (extracted\n * in issue #286 Gap 2 when `lambda-resolver.ts` needed the same\n * resolver). The shared type carries `pseudoParameters` (Tier 1) +\n * `stateResources` (Tier 2). `stateResources` is consumed by:\n * - Image (PR #267): `${<LogicalId>}` against an `AWS::ECR::Repository`\n * and the `Fn::GetAtt: [<Repo>, 'RepositoryUri']` shape.\n * - Environment / Secrets (issue #291): intrinsic-valued\n * `Environment[].Value` and `Secrets[].ValueFrom` entries\n * (`Ref` / `Fn::GetAtt` / `Fn::Sub` / `Fn::Join`) are substituted\n * via `state-resolver.ts`'s `substituteAgainstState`.\n * Existing consumers (`src/cli/commands/local-run-task.ts`) import the\n * alias; new code should reach for `ImageResolutionContext` directly.\n */\nexport type EcsImageResolutionContext = ImageResolutionContext;\n\n/**\n * Parse a `target` argument into (optional stack pattern, path-or-id).\n * Mirrors `lambda-resolver.parseTarget` exactly — same accepted forms,\n * same single-stack auto-detect rule.\n */\nexport interface ParsedEcsTarget {\n stackPattern: string | null;\n pathOrId: string;\n isPath: boolean;\n}\n\n/**\n * Walk the matched stack's template and report whether any container's\n * `Image` field needs Tier 1 (pseudo-parameter) or Tier 2 (state-recorded\n * ECR Repository) substitution. The CLI uses this to make the STS /\n * state-load calls lazy — flat strings / cdk-asset shapes / Fn::Sub bodies\n * with no recognized placeholders skip both calls entirely.\n *\n * The probe is per-stack rather than per-target because we don't run the\n * full target resolver until the substitution context is built; cheap\n * O(resources) scan over the template's task definitions is sufficient.\n */\nexport interface EcsImageResolutionNeeds {\n /** Any `Fn::Sub` body references an `AWS::*` pseudo parameter. */\n needsPseudoParameters: boolean;\n /**\n * Any `Fn::Sub` body references an `AWS::ECR::Repository` logical ID,\n * OR any `Fn::GetAtt: [<Repo>, 'RepositoryUri' | 'Arn']` is present.\n */\n needsStateResources: boolean;\n /**\n * Any container's `Environment[].Value` OR `Secrets[].ValueFrom` is\n * an intrinsic (`Ref` / `Fn::GetAtt` / `Fn::Sub` / `Fn::Join`); OR\n * any task's `Volumes[].Host.SourcePath` is an intrinsic (Gap 6 of\n * #286); OR any task's `TaskRoleArn` / `ExecutionRoleArn` is a\n * `Fn::Sub` / `Fn::Join` intrinsic (Gap 5 of #286). The CLI uses\n * this flag to gate `--from-state` state loading + pseudo-parameter\n * resolution. Without `--from-state` these entries either drop with\n * warnings (env / secret / role ARN) or hard-error (volume source\n * path — silent drop would mount an empty anonymous volume).\n */\n needsEnvOrSecretSubstitution: boolean;\n /**\n * Any container's `Environment[].Value` OR `Secrets[].ValueFrom` is a\n * cross-stack intrinsic (top-level `Fn::ImportValue` /\n * `Fn::GetStackOutput`). Issue #454 — gates the cross-stack resolver\n * construction inside the `--from-state` flow so literal +\n * same-stack-intrinsic env maps don't pay the extra S3-client / index-\n * load cost.\n */\n needsCrossStackResolver: boolean;\n}\n\nexport function detectEcsImageResolutionNeeds(stack: StackInfo): EcsImageResolutionNeeds {\n const resources = stack.template.Resources ?? {};\n let needsPseudoParameters = false;\n let needsStateResources = false;\n let needsEnvOrSecretSubstitution = false;\n let needsCrossStackResolver = false;\n\n for (const res of Object.values(resources)) {\n if (res.Type !== 'AWS::ECS::TaskDefinition') continue;\n const props = res.Properties ?? {};\n const containers = Array.isArray(props['ContainerDefinitions'])\n ? props['ContainerDefinitions']\n : [];\n for (const c of containers) {\n if (!c || typeof c !== 'object') continue;\n const co = c as Record<string, unknown>;\n const image = co['Image'];\n const need = inspectImageForSubstitutions(image, resources);\n if (need.pseudo) needsPseudoParameters = true;\n if (need.state) needsStateResources = true;\n if (containerHasIntrinsicEnvOrSecret(co)) needsEnvOrSecretSubstitution = true;\n if (containerHasCrossStackEnvOrSecret(co)) {\n needsEnvOrSecretSubstitution = true;\n needsCrossStackResolver = true;\n }\n }\n // Volumes[].Host.SourcePath as a CFn intrinsic (Gap 6 of #286).\n const rawVolumes = props['Volumes'];\n if (Array.isArray(rawVolumes)) {\n for (const v of rawVolumes) {\n if (volumeHasIntrinsicSourcePath(v)) {\n needsEnvOrSecretSubstitution = true;\n break;\n }\n }\n }\n // TaskRoleArn / ExecutionRoleArn as a Fn::Sub / Fn::Join intrinsic\n // (Gap 5 of #286). Ref / Fn::GetAtt shapes against AWS::IAM::Role\n // resolve to a placeholder ARN without state, so they don't trigger\n // state loading.\n if (\n isFnJoinOrSubIntrinsic(props['TaskRoleArn']) ||\n isFnJoinOrSubIntrinsic(props['ExecutionRoleArn'])\n ) {\n needsEnvOrSecretSubstitution = true;\n }\n }\n return {\n needsPseudoParameters,\n needsStateResources,\n needsEnvOrSecretSubstitution,\n needsCrossStackResolver,\n };\n}\n\n/**\n * Returns true when any `Environment[].Value` or `Secrets[].ValueFrom`\n * is a top-level `Fn::ImportValue` / `Fn::GetStackOutput` intrinsic.\n * Issue #454 — gates cross-stack resolver construction so literal +\n * same-stack-intrinsic env / secret maps don't pay the extra cost.\n */\nfunction containerHasCrossStackEnvOrSecret(c: Record<string, unknown>): boolean {\n const env = c['Environment'];\n if (Array.isArray(env)) {\n for (const entry of env) {\n if (!entry || typeof entry !== 'object') continue;\n const v = (entry as Record<string, unknown>)['Value'];\n if (isCrossStackIntrinsic(v)) return true;\n }\n }\n const secrets = c['Secrets'];\n if (Array.isArray(secrets)) {\n for (const entry of secrets) {\n if (!entry || typeof entry !== 'object') continue;\n const v = (entry as Record<string, unknown>)['ValueFrom'];\n if (isCrossStackIntrinsic(v)) return true;\n }\n }\n return false;\n}\n\nfunction isCrossStackIntrinsic(value: unknown): boolean {\n if (!value || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n return 'Fn::ImportValue' in obj || 'Fn::GetStackOutput' in obj;\n}\n\n/**\n * Detect whether a Volume entry has an intrinsic-valued `Host.SourcePath`.\n * Used by `detectEcsImageResolutionNeeds` (Gap 6 of #286) to trigger\n * state-load + pseudo-parameter resolution under `--from-state` when the\n * SourcePath is built via `cdk.Fn.sub(...)` / `cdk.Fn.join(...)`.\n */\nfunction volumeHasIntrinsicSourcePath(v: unknown): boolean {\n if (!v || typeof v !== 'object') return false;\n const host = (v as Record<string, unknown>)['Host'];\n if (!host || typeof host !== 'object') return false;\n const sp = (host as Record<string, unknown>)['SourcePath'];\n if (sp === undefined || sp === null) return false;\n if (typeof sp === 'string') return false;\n return typeof sp === 'object';\n}\n\n/**\n * Detect whether a value is an `Fn::Sub` / `Fn::Join` intrinsic. Used by\n * `detectEcsImageResolutionNeeds` to flag role-ARN intrinsic shapes\n * (Gap 5 of #286) that need `--from-state` + pseudo-parameter\n * substitution. `Ref` / `Fn::GetAtt` shapes resolve to a placeholder\n * ARN without state and are intentionally excluded here.\n */\nfunction isFnJoinOrSubIntrinsic(value: unknown): boolean {\n if (!value || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n return 'Fn::Join' in obj || 'Fn::Sub' in obj;\n}\n\n/**\n * Returns true when any `Environment[].Value` or `Secrets[].ValueFrom`\n * is an intrinsic (non-literal). Used to gate `--from-state` state\n * loading at the CLI layer — issue #291.\n */\nfunction containerHasIntrinsicEnvOrSecret(c: Record<string, unknown>): boolean {\n const env = c['Environment'];\n if (Array.isArray(env)) {\n for (const entry of env) {\n if (!entry || typeof entry !== 'object') continue;\n const v = (entry as Record<string, unknown>)['Value'];\n if (\n v !== undefined &&\n typeof v !== 'string' &&\n typeof v !== 'number' &&\n typeof v !== 'boolean'\n ) {\n return true;\n }\n }\n }\n const secrets = c['Secrets'];\n if (Array.isArray(secrets)) {\n for (const entry of secrets) {\n if (!entry || typeof entry !== 'object') continue;\n const v = (entry as Record<string, unknown>)['ValueFrom'];\n if (v !== undefined && typeof v !== 'string') return true;\n }\n }\n return false;\n}\n\nfunction inspectImageForSubstitutions(\n image: unknown,\n resources: Record<string, TemplateResource>\n): { pseudo: boolean; state: boolean } {\n if (!image || typeof image !== 'object') return { pseudo: false, state: false };\n const obj = image as Record<string, unknown>;\n\n // Fn::GetAtt direct shape against an ECR::Repository — Tier 2 only.\n const getAtt = obj['Fn::GetAtt'];\n if (getAtt !== undefined) {\n let lid: string | undefined;\n if (Array.isArray(getAtt) && typeof getAtt[0] === 'string') lid = getAtt[0];\n else if (typeof getAtt === 'string') lid = getAtt.split('.')[0];\n if (lid && resources[lid]?.Type === 'AWS::ECR::Repository') {\n return { pseudo: false, state: true };\n }\n }\n\n // Fn::Join body: recursively walk every element for references that\n // would trigger Tier 1 (pseudo) / Tier 2 (state) needs. CDK 2.x\n // synthesizes `ContainerImage.fromEcrRepository(repo)` as a Fn::Join\n // containing nested Fn::Select / Fn::Split over the repo's Arn GetAtt\n // plus a Ref to the repo and `Ref: AWS::URLSuffix`.\n const join = obj['Fn::Join'];\n if (Array.isArray(join) && join.length === 2 && Array.isArray(join[1])) {\n const scan: { pseudo: boolean; state: boolean } = { pseudo: false, state: false };\n inspectIntrinsicNeeds(join[1], resources, scan);\n if (scan.pseudo || scan.state) return scan;\n }\n\n // Fn::Sub body: scan every `${...}` placeholder.\n let sub: string | undefined;\n const subRaw = obj['Fn::Sub'];\n if (typeof subRaw === 'string') sub = subRaw;\n else if (Array.isArray(subRaw) && typeof subRaw[0] === 'string') sub = subRaw[0];\n if (!sub) return { pseudo: false, state: false };\n\n let pseudo = false;\n let state = false;\n const placeholderRegex = /\\$\\{([^}]+)\\}/g;\n let m: RegExpExecArray | null;\n while ((m = placeholderRegex.exec(sub)) !== null) {\n const key = m[1]!;\n if (key.startsWith('AWS::')) {\n pseudo = true;\n continue;\n }\n const dot = key.indexOf('.');\n const lid = dot === -1 ? key : key.slice(0, dot);\n if (resources[lid]?.Type === 'AWS::ECR::Repository') state = true;\n }\n return { pseudo, state };\n}\n\n/**\n * Recursive needs probe used by `inspectImageForSubstitutions` for the\n * `Fn::Join` shape: walk every nested intrinsic and flag whether a\n * `Ref: AWS::*` pseudo parameter is reachable (Tier 1 needed) or a\n * `Ref` / `Fn::GetAtt` against an `AWS::ECR::Repository` is reachable\n * (Tier 2 needed).\n */\nfunction inspectIntrinsicNeeds(\n node: unknown,\n resources: Record<string, TemplateResource>,\n scan: { pseudo: boolean; state: boolean }\n): void {\n if (node === null || node === undefined) return;\n if (typeof node === 'string' || typeof node === 'number' || typeof node === 'boolean') return;\n if (Array.isArray(node)) {\n for (const item of node) inspectIntrinsicNeeds(item, resources, scan);\n return;\n }\n if (typeof node !== 'object') return;\n const obj = node as Record<string, unknown>;\n\n if (typeof obj['Ref'] === 'string') {\n const target = obj['Ref'];\n if (target.startsWith('AWS::')) scan.pseudo = true;\n else if (resources[target]?.Type === 'AWS::ECR::Repository') scan.state = true;\n return;\n }\n\n const getAtt = obj['Fn::GetAtt'];\n if (getAtt !== undefined) {\n let lid: string | undefined;\n if (Array.isArray(getAtt) && typeof getAtt[0] === 'string') lid = getAtt[0];\n else if (typeof getAtt === 'string') lid = getAtt.split('.')[0];\n if (lid && resources[lid]?.Type === 'AWS::ECR::Repository') scan.state = true;\n return;\n }\n\n // For every other intrinsic shape, descend into its arguments so a\n // `Fn::Select` / `Fn::Split` / `Fn::Join` / `Fn::Sub` wrapper does\n // not hide a reference deeper in the tree.\n for (const value of Object.values(obj)) {\n inspectIntrinsicNeeds(value, resources, scan);\n }\n}\n\nexport function parseEcsTarget(target: string): ParsedEcsTarget {\n if (typeof target !== 'string' || target.length === 0) {\n throw new EcsTaskResolutionError(\n \"Empty target. Pass a CDK display path (e.g. 'MyStack/MyService/TaskDef') or stack-qualified logical ID (e.g. 'MyStack:MyServiceTaskDefXYZ1234').\"\n );\n }\n const colonIdx = target.indexOf(':');\n const slashIdx = target.indexOf('/');\n if (colonIdx > 0 && (slashIdx === -1 || colonIdx < slashIdx)) {\n const stackPattern = target.substring(0, colonIdx);\n const pathOrId = target.substring(colonIdx + 1);\n if (pathOrId.length === 0) {\n throw new EcsTaskResolutionError(`Target '${target}' has no logical ID after ':'.`);\n }\n return { stackPattern, pathOrId, isPath: pathOrId.includes('/') };\n }\n if (slashIdx > 0) {\n return { stackPattern: target.substring(0, slashIdx), pathOrId: target, isPath: true };\n }\n return { stackPattern: null, pathOrId: target, isPath: false };\n}\n\n/**\n * Resolve a parsed target against the synthesized stacks. Throws\n * `EcsTaskResolutionError` with an actionable message (listing every\n * available task definition in the matched stack) on any miss.\n *\n * Optional `context` (issue #264): when the caller can supply AWS\n * pseudo-parameter values (Tier 1) and / or host state-recorded resources\n * (Tier 2), `Fn::Sub`-shaped ECR image URIs that reference\n * `${AWS::AccountId}` / `${AWS::Region}` / a same-stack\n * `AWS::ECR::Repository` are substituted before classification.\n */\nexport function resolveEcsTaskTarget(\n target: string,\n stacks: StackInfo[],\n context?: EcsImageResolutionContext\n): ResolvedEcsTask {\n if (stacks.length === 0) {\n throw new EcsTaskResolutionError('No stacks found in the synthesized assembly.');\n }\n const parsed = parseEcsTarget(target);\n const stack = pickStack(parsed, stacks);\n const resources = stack.template.Resources ?? {};\n\n let logicalId: string | undefined;\n let resource: TemplateResource | undefined;\n\n if (parsed.isPath) {\n const index = buildCdkPathIndex(stack.template);\n const resolved = resolveCdkPathToLogicalIds(parsed.pathOrId, index);\n const taskDefs = resolved.filter(\n ({ logicalId: l }) => resources[l]?.Type === 'AWS::ECS::TaskDefinition'\n );\n if (taskDefs.length === 0) {\n throw notFoundError(target, stack, resources);\n }\n if (taskDefs.length > 1) {\n throw new EcsTaskResolutionError(\n `Target '${target}' matches ${taskDefs.length} task definitions in ${stack.stackName}: ` +\n taskDefs.map((t) => t.logicalId).join(', ') +\n '. Refine the path or use the stack:LogicalId form.'\n );\n }\n logicalId = taskDefs[0]!.logicalId;\n resource = resources[logicalId];\n } else {\n resource = resources[parsed.pathOrId];\n if (!resource) throw notFoundError(target, stack, resources);\n logicalId = parsed.pathOrId;\n }\n\n if (!logicalId || !resource) throw notFoundError(target, stack, resources);\n\n if (resource.Type === 'AWS::Lambda::Function') {\n throw new EcsTaskResolutionError(\n `Resource '${logicalId}' in ${stack.stackName} is a Lambda function, not an ECS task definition. ` +\n `Use \\`${getEmbedConfig().cliName} invoke\\` for Lambda; \\`${getEmbedConfig().cliName} run-task\\` is ECS only.`\n );\n }\n if (resource.Type !== 'AWS::ECS::TaskDefinition') {\n throw new EcsTaskResolutionError(\n `Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not an AWS::ECS::TaskDefinition.`\n );\n }\n\n return extractTaskDefinitionProperties(stack, logicalId, resource, context);\n}\n\nfunction pickStack(parsed: ParsedEcsTarget, stacks: StackInfo[]): StackInfo {\n if (parsed.stackPattern === null) {\n if (stacks.length === 1) return stacks[0]!;\n throw new EcsTaskResolutionError(\n `Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. ` +\n `Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). ` +\n `Available stacks: ${stacks.map((s) => s.stackName).join(', ')}.`\n );\n }\n const matched = matchStacks(stacks, [parsed.stackPattern]);\n if (matched.length === 0) {\n throw new EcsTaskResolutionError(\n `Stack '${parsed.stackPattern}' not found. ` +\n `Available stacks: ${stacks.map((s) => s.stackName).join(', ')}.`\n );\n }\n if (matched.length > 1) {\n throw new EcsTaskResolutionError(\n `Stack pattern '${parsed.stackPattern}' matched ${matched.length} stacks: ` +\n matched.map((s) => s.stackName).join(', ') +\n '. Use a more specific pattern.'\n );\n }\n return matched[0]!;\n}\n\nfunction extractTaskDefinitionProperties(\n stack: StackInfo,\n logicalId: string,\n resource: TemplateResource,\n context?: EcsImageResolutionContext\n): ResolvedEcsTask {\n const props = resource.Properties ?? {};\n const warnings: string[] = [];\n\n const family = pickString(props['Family']) ?? logicalId;\n\n const rawNetworkMode = pickString(props['NetworkMode']) ?? 'bridge';\n let networkMode: ResolvedEcsTask['networkMode'];\n if (\n rawNetworkMode === 'bridge' ||\n rawNetworkMode === 'awsvpc' ||\n rawNetworkMode === 'host' ||\n rawNetworkMode === 'none'\n ) {\n networkMode = rawNetworkMode;\n } else {\n throw new EcsTaskResolutionError(\n `Task definition '${logicalId}' has unsupported NetworkMode '${rawNetworkMode}'. ` +\n 'Supported values: bridge / awsvpc / host / none.'\n );\n }\n if (networkMode === 'awsvpc') {\n warnings.push(\n `NetworkMode 'awsvpc' on '${logicalId}' is mapped to docker bridge locally — ` +\n 'docker cannot emulate ENI-per-task. AWS SDK calls still reach public endpoints via the developer network.'\n );\n }\n\n // Issue #544 — `ProxyConfiguration` (custom App Mesh / Envoy bootstrap)\n // is NOT honored by `cdkl start-service` / `cdkl run-task`\n // in v1. Design § 2 hard-rejects custom Envoy bootstrap; locally we\n // run the user's containers without the configured proxy and surface\n // a warn so users aren't surprised by the missing sidecar.\n if (props['ProxyConfiguration']) {\n warnings.push(\n `Task definition '${logicalId}' declares 'ProxyConfiguration' (custom Envoy / App Mesh bootstrap), ` +\n `which is NOT honored by '${getEmbedConfig().cliName} start-service' / '${getEmbedConfig().cliName} run-task' in v1. ` +\n 'Local execution will run without the configured proxy. See design doc § 2 for the rationale.'\n );\n }\n\n const resources = stack.template.Resources ?? {};\n\n const subContext = buildSubstitutionContextFromImageContext(context);\n const taskRoleArn = resolveRoleArn(props['TaskRoleArn'], resources, subContext);\n const executionRoleArn = resolveRoleArn(props['ExecutionRoleArn'], resources, subContext);\n\n const runtimePlatform = parseRuntimePlatform(props['RuntimePlatform']);\n\n const rawContainers = props['ContainerDefinitions'];\n if (!Array.isArray(rawContainers) || rawContainers.length === 0) {\n throw new EcsTaskResolutionError(`Task definition '${logicalId}' has no ContainerDefinitions.`);\n }\n const containers = rawContainers.map((c, idx) =>\n parseContainerDefinition(c, idx, logicalId, resources, stack, context)\n );\n\n // Surface per-container warnings (e.g. dropped intrinsic env vars /\n // secrets) on the task-level `warnings` array so the CLI prints them.\n for (const ctr of containers) {\n for (const w of ctr.warnings) {\n warnings.push(`Container '${ctr.name}': ${w}`);\n }\n }\n\n const rawVolumes = props['Volumes'];\n const volumes = Array.isArray(rawVolumes)\n ? rawVolumes.map((v, idx) => parseVolume(v, idx, logicalId, subContext))\n : [];\n\n // dependsOn must reference an existing container name.\n const containerNames = new Set(containers.map((c) => c.name));\n for (const c of containers) {\n for (const d of c.dependsOn) {\n if (!containerNames.has(d.containerName)) {\n throw new EcsTaskResolutionError(\n `Container '${c.name}' depends on '${d.containerName}', which is not defined in task '${logicalId}'.`\n );\n }\n }\n }\n\n const out: ResolvedEcsTask = {\n stack,\n taskDefinitionLogicalId: logicalId,\n resource,\n family,\n networkMode,\n containers,\n volumes,\n warnings,\n };\n if (taskRoleArn !== undefined) out.taskRoleArn = taskRoleArn;\n if (executionRoleArn !== undefined) out.executionRoleArn = executionRoleArn;\n if (runtimePlatform !== undefined) out.runtimePlatform = runtimePlatform;\n return out;\n}\n\nfunction parseRuntimePlatform(value: unknown): ResolvedEcsTask['runtimePlatform'] | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const obj = value as Record<string, unknown>;\n const cpu = obj['CpuArchitecture'];\n const os = obj['OperatingSystemFamily'];\n if (typeof cpu !== 'string' || typeof os !== 'string') return undefined;\n if (cpu !== 'X86_64' && cpu !== 'ARM64') return undefined;\n if (os !== 'LINUX') return undefined;\n return { cpuArchitecture: cpu, operatingSystemFamily: os };\n}\n\nfunction parseContainerDefinition(\n raw: unknown,\n idx: number,\n taskLogicalId: string,\n resources: Record<string, TemplateResource>,\n stack: StackInfo,\n context?: EcsImageResolutionContext\n): ResolvedEcsContainer {\n if (!raw || typeof raw !== 'object') {\n throw new EcsTaskResolutionError(\n `Task '${taskLogicalId}' ContainerDefinitions[${idx}] is not an object.`\n );\n }\n const c = raw as Record<string, unknown>;\n const name = pickString(c['Name']);\n if (!name) {\n throw new EcsTaskResolutionError(\n `Task '${taskLogicalId}' ContainerDefinitions[${idx}] has no Name.`\n );\n }\n\n const image = parseContainerImage(c['Image'], name, taskLogicalId, resources, stack, context);\n\n const command = pickStringArray(c['Command']);\n const entryPoint = pickStringArray(c['EntryPoint']);\n const workingDirectory = pickString(c['WorkingDirectory']);\n\n const subContext = buildSubstitutionContextFromImageContext(context);\n const environment: Record<string, string> = {};\n const sensitiveEnvKeys: string[] = [];\n const droppedEnvKeys: { key: string; reason: string }[] = [];\n if (Array.isArray(c['Environment'])) {\n for (const entry of c['Environment'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const e = entry as Record<string, unknown>;\n const key = pickString(e['Name']);\n const value = e['Value'];\n if (!key) continue;\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n environment[key] = String(value);\n continue;\n }\n // Intrinsic-valued entry. With `--from-state` we try to substitute\n // against state + pseudo parameters (closes #291); without it the\n // value is dropped and a warn is surfaced via the task's\n // `warnings` array (matches PR 1 `cdkl invoke` semantics).\n if (subContext) {\n const { result: sub, consumedSensitive } = resolveWithSensitivity(value, subContext);\n if (sub.kind === 'literal') {\n environment[key] = String(sub.value);\n // SecureString-backed value — keep it off the `docker run` argv (#99).\n if (consumedSensitive) sensitiveEnvKeys.push(key);\n continue;\n }\n droppedEnvKeys.push({ key, reason: sub.reason });\n } else {\n droppedEnvKeys.push({\n key,\n reason: 'intrinsic-valued; pass --from-state to substitute against deployed state',\n });\n }\n }\n }\n\n const secrets: ResolvedEcsContainer['secrets'] = [];\n const droppedSecretKeys: { key: string; reason: string }[] = [];\n if (Array.isArray(c['Secrets'])) {\n for (const entry of c['Secrets'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const e = entry as Record<string, unknown>;\n const sName = pickString(e['Name']);\n const valueFromRaw = e['ValueFrom'];\n if (!sName) continue;\n // Literal string ValueFrom (pre-resolved, or fromSecretCompleteArn).\n if (typeof valueFromRaw === 'string' && valueFromRaw.length > 0) {\n secrets.push({ name: sName, valueFrom: valueFromRaw });\n continue;\n }\n // Intrinsic-valued (`Ref` / `Fn::GetAtt` / `Fn::Join` / `Fn::Sub`).\n // With `--from-state` substitute against deployed state + pseudo\n // parameters; without it, drop the secret and warn — the user's\n // workaround is `fromSecretCompleteArn` (literal ARN at synth time).\n if (subContext) {\n const sub = substituteAgainstState(valueFromRaw, subContext);\n if (sub.kind === 'literal' && typeof sub.value === 'string' && sub.value.length > 0) {\n secrets.push({ name: sName, valueFrom: sub.value });\n continue;\n }\n droppedSecretKeys.push({\n key: sName,\n reason: sub.kind === 'literal' ? 'resolved to non-string / empty value' : sub.reason,\n });\n } else {\n droppedSecretKeys.push({\n key: sName,\n reason: 'intrinsic-valued ValueFrom; pass --from-state to resolve the deployed ARN',\n });\n }\n }\n }\n\n const portMappings: ResolvedEcsContainer['portMappings'] = [];\n if (Array.isArray(c['PortMappings'])) {\n for (const entry of c['PortMappings'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const p = entry as Record<string, unknown>;\n const containerPort = typeof p['ContainerPort'] === 'number' ? p['ContainerPort'] : undefined;\n if (containerPort === undefined) continue;\n const hostPort = typeof p['HostPort'] === 'number' ? p['HostPort'] : undefined;\n const protocol = pickString(p['Protocol']) === 'udp' ? 'udp' : 'tcp';\n const pm: ResolvedEcsContainer['portMappings'][number] = { containerPort, protocol };\n if (hostPort !== undefined) pm.hostPort = hostPort;\n const portName = typeof p['Name'] === 'string' ? p['Name'] : undefined;\n if (portName !== undefined) pm.name = portName;\n portMappings.push(pm);\n }\n }\n\n const mountPoints: ResolvedEcsContainer['mountPoints'] = [];\n if (Array.isArray(c['MountPoints'])) {\n for (const entry of c['MountPoints'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const m = entry as Record<string, unknown>;\n const sourceVolume = pickString(m['SourceVolume']);\n const containerPath = pickString(m['ContainerPath']);\n if (!sourceVolume || !containerPath) continue;\n mountPoints.push({\n sourceVolume,\n containerPath,\n readOnly: m['ReadOnly'] === true,\n });\n }\n }\n\n const dependsOn: ResolvedEcsContainer['dependsOn'] = [];\n if (Array.isArray(c['DependsOn'])) {\n for (const entry of c['DependsOn'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const d = entry as Record<string, unknown>;\n const containerName = pickString(d['ContainerName']);\n const condition = pickString(d['Condition']);\n if (!containerName || !condition) continue;\n if (\n condition !== 'START' &&\n condition !== 'COMPLETE' &&\n condition !== 'SUCCESS' &&\n condition !== 'HEALTHY'\n ) {\n throw new EcsTaskResolutionError(\n `Container '${name}' has invalid DependsOn condition '${condition}'. ` +\n 'Accepted values: START / COMPLETE / SUCCESS / HEALTHY.'\n );\n }\n dependsOn.push({ containerName, condition });\n }\n }\n\n const links = pickStringArray(c['Links']) ?? [];\n const essential = c['Essential'] === false ? false : true;\n\n let healthCheck: ResolvedEcsContainer['healthCheck'] | undefined;\n if (c['HealthCheck'] && typeof c['HealthCheck'] === 'object') {\n const h = c['HealthCheck'] as Record<string, unknown>;\n const command2 = pickStringArray(h['Command']);\n if (command2 && command2.length > 0) {\n healthCheck = { command: command2 };\n if (typeof h['Interval'] === 'number') healthCheck.interval = h['Interval'];\n if (typeof h['Timeout'] === 'number') healthCheck.timeout = h['Timeout'];\n if (typeof h['Retries'] === 'number') healthCheck.retries = h['Retries'];\n if (typeof h['StartPeriod'] === 'number') healthCheck.startPeriod = h['StartPeriod'];\n }\n }\n\n const user = pickString(c['User']);\n const privileged = c['Privileged'] === true ? true : undefined;\n const readonlyRootFilesystem = c['ReadonlyRootFilesystem'] === true ? true : undefined;\n\n const ulimits: ResolvedEcsContainer['ulimits'] = [];\n if (Array.isArray(c['Ulimits'])) {\n for (const entry of c['Ulimits'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const u = entry as Record<string, unknown>;\n const uName = pickString(u['Name']);\n const soft = typeof u['SoftLimit'] === 'number' ? u['SoftLimit'] : undefined;\n const hard = typeof u['HardLimit'] === 'number' ? u['HardLimit'] : undefined;\n if (!uName || soft === undefined || hard === undefined) continue;\n ulimits.push({ name: uName, softLimit: soft, hardLimit: hard });\n }\n }\n\n const warnings: string[] = [];\n for (const d of droppedEnvKeys) {\n warnings.push(`Environment '${d.key}' dropped: ${d.reason}`);\n }\n for (const d of droppedSecretKeys) {\n warnings.push(`Secret '${d.key}' dropped: ${d.reason}`);\n }\n\n const out: ResolvedEcsContainer = {\n name,\n image,\n environment,\n sensitiveEnvKeys,\n secrets,\n portMappings,\n mountPoints,\n dependsOn,\n links,\n essential,\n ulimits,\n warnings,\n };\n if (command !== undefined) out.command = command;\n if (entryPoint !== undefined) out.entryPoint = entryPoint;\n if (workingDirectory !== undefined) out.workingDirectory = workingDirectory;\n if (healthCheck !== undefined) out.healthCheck = healthCheck;\n if (user !== undefined) out.user = user;\n if (privileged !== undefined) out.privileged = privileged;\n if (readonlyRootFilesystem !== undefined) out.readonlyRootFilesystem = readonlyRootFilesystem;\n return out;\n}\n\n/**\n * Map the ECS image-resolution context's `stateResources` +\n * `pseudoParameters` into the shape `substituteAgainstState` expects\n * (closes #291). Returns `undefined` when state has not been loaded —\n * the caller falls back to the pre-PR literal-only path.\n */\nfunction buildSubstitutionContextFromImageContext(\n context: EcsImageResolutionContext | undefined\n): SubstitutionContext | undefined {\n if (!context?.stateResources) return undefined;\n const subContext: SubstitutionContext = { resources: context.stateResources };\n if (context.pseudoParameters) {\n subContext.pseudoParameters = { ...context.pseudoParameters };\n }\n if (context.stateParameters) {\n subContext.parameters = { ...context.stateParameters };\n }\n if (context.stateSensitiveParameters?.length) {\n subContext.sensitiveParameters = new Set(context.stateSensitiveParameters);\n }\n return subContext;\n}\n\n/**\n * Parse the `Image` field of an ECS container definition.\n *\n * Three shapes:\n * - `<account>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` — same-account\n * same-region ECR. Cross-account/region is hard-errored (matches\n * `cdkl invoke`'s ECR-pull semantics).\n * - `Fn::Sub` / `Fn::Join` / `Ref` referencing a `Code.fromAsset`-style\n * CDK asset image. Surfaces `kind: 'cdk-asset'` with the optional\n * asset hash so the runner can route through `docker-build.ts`.\n * - Any other public URI (`public.ecr.aws/...`, `docker.io/...`,\n * `nginx:latest`, etc.) — `kind: 'public'`, runner does a `docker pull`.\n *\n * Two-tier substitution (issue #264):\n * - **Tier 1** — when `context.pseudoParameters` is populated, AWS pseudo\n * parameters in `Fn::Sub` bodies (`${AWS::AccountId}` / `${AWS::Region}` /\n * `${AWS::Partition}` / `${AWS::URLSuffix}`) are substituted before\n * regex matching. The CLI resolves these via STS + region env once\n * per invocation.\n * - **Tier 2** — when `context.stateResources` is populated\n * (`--from-state`), `${<LogicalId>}` placeholders that resolve to an\n * `AWS::ECR::Repository` are substituted with the state-recorded\n * `physicalId`, and `Fn::GetAtt: [<Repo>, 'RepositoryUri']` shapes\n * are resolved via the same state record.\n *\n * Cross-account / cross-region pull (#455): `pullEcrImage` auto-detects\n * cross-account from `sts:GetCallerIdentity` and authenticates against\n * the URI's region directly. Pass `--ecr-role-arn <arn>` when the caller\n * does not already have cross-account `ecr:GetAuthorizationToken` /\n * `ecr:BatchGetImage` access on the target repository.\n */\nfunction parseContainerImage(\n raw: unknown,\n containerName: string,\n taskLogicalId: string,\n resources: Record<string, TemplateResource>,\n _stack: StackInfo,\n context?: EcsImageResolutionContext\n): ResolvedEcsImage {\n // Tier 2: handle `Fn::GetAtt: [<Repo>, 'RepositoryUri']` directly — it\n // produces a complete `<acct>.dkr.ecr.<region>.amazonaws.com/<name>` URI\n // we can match against the ECR regex below. The tag is whatever the\n // template provides; for the bare GetAtt shape there is none, which is\n // unusual but we surface the resulting tagless URI rather than guess.\n const getAttImage = tryResolveImageGetAtt(raw, resources, context);\n if (getAttImage) {\n return classifyResolvedImage(getAttImage);\n }\n\n // Issue #271: CDK 2.x synthesizes `ContainerImage.fromEcrRepository(repo)`\n // as a `Fn::Join` that builds the ECR URI from nested `Fn::Select` /\n // `Fn::Split` over the repository's `Arn` GetAtt plus a `Ref` to the\n // repo and `Ref: AWS::URLSuffix`. The repository's account-id and\n // region are only available from deployed-stack state (recorded at\n // deploy time), so this shape inherently requires loading state via\n // `--from-cfn-stack` (Tier 2).\n const joinResolved = tryResolveImageFnJoin(raw, resources, context);\n if (joinResolved.kind === 'resolved') {\n return classifyResolvedImage(joinResolved.uri);\n }\n if (joinResolved.kind === 'needs-state') {\n throw new EcsTaskResolutionError(\n `Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. ` +\n `${getEmbedConfig().cliName} run-task cannot resolve the repository URI without state — ` +\n 'pass --from-cfn-stack to load the deployed stack state, ' +\n 'build via ContainerImage.fromAsset, or pin a public image.'\n );\n }\n if (joinResolved.kind === 'unsupported-join') {\n throw new EcsTaskResolutionError(\n `Container '${containerName}' in task '${taskLogicalId}' has an unsupported Fn::Join Image shape: ${joinResolved.reason}. ` +\n `${getEmbedConfig().cliName} run-task recognizes the canonical CDK 2.x ContainerImage.fromEcrRepository Fn::Join shape ` +\n '(delimiter \"\" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).'\n );\n }\n\n const flat = extractImageString(raw);\n if (!flat) {\n throw new EcsTaskResolutionError(\n `Container '${containerName}' in task '${taskLogicalId}' has an unparseable Image property. ` +\n `${getEmbedConfig().cliName} run-task v1 supports flat string images, single-key Fn::Sub bodies, and CDK-asset Image references.`\n );\n }\n\n // CDK asset shape: contains the bootstrap-assets repo placeholder. The\n // tail `:<hash>` is the asset hash (same shape used by Lambda container\n // images — see `getDockerImageBySourceHash`).\n if (flat.includes('cdk-hnb659fds-container-assets-')) {\n const hashMatch = /:([a-f0-9]{8,})$/.exec(flat);\n const out: ResolvedEcsImage = { kind: 'cdk-asset' };\n if (hashMatch) out.assetHash = hashMatch[1]!;\n return out;\n }\n\n // Substitute pseudo parameters + same-stack ECR Ref placeholders into\n // the flat string when context is supplied. Pure string-rewrite —\n // unresolved placeholders pass through verbatim so the post-walk\n // diagnostics below can route to the precise error message.\n const substituted = substituteImagePlaceholders(flat, resources, context);\n\n // Unresolved `${...}` placeholders survived substitution. Surface a\n // precise hint BEFORE the ECR regex match — otherwise a leftover\n // `${MyRepo}` in the path portion would still match the host-portion\n // regex and silently produce a broken URI for `docker pull` to fail on.\n if (substituted.includes('${')) {\n const unresolvedRepoRef = findUnresolvedEcrRepositoryRef(substituted, resources);\n if (unresolvedRepoRef) {\n throw new EcsTaskResolutionError(\n `Container '${containerName}' in task '${taskLogicalId}' references same-stack ECR repository '${unresolvedRepoRef}'. ` +\n `${getEmbedConfig().cliName} run-task v1 cannot resolve the repository URI without state — ` +\n 'pass --from-cfn-stack to load the deployed stack state, ' +\n 'build via ContainerImage.fromAsset, or pin a public image.'\n );\n }\n if (substituted.includes('AWS::')) {\n throw new EcsTaskResolutionError(\n `Container '${containerName}' in task '${taskLogicalId}' has an Image that references AWS pseudo parameters (${substituted}). ` +\n `${getEmbedConfig().productName} could not resolve them: confirm AWS credentials are configured so STS GetCallerIdentity succeeds, ` +\n 'and that --region / AWS_REGION names the target region. ' +\n 'Workaround: build the image locally (ContainerImage.fromAsset) or pin a public image.'\n );\n }\n // Some other placeholder survived (e.g. a Parameter ref). Surface as\n // a generic resolver failure rather than letting it slip through as\n // a \"public\" image.\n throw new EcsTaskResolutionError(\n `Container '${containerName}' in task '${taskLogicalId}' has an Image with unresolved \\${...} placeholders (${substituted}). ` +\n `${getEmbedConfig().cliName} run-task v1 only resolves AWS pseudo parameters and same-stack AWS::ECR::Repository refs.`\n );\n }\n\n // Account-scoped ECR repo.\n const ecrMatch = /^(\\d{12})\\.dkr\\.ecr\\.([^.]+)\\.amazonaws\\.com(?:\\.cn)?\\//.exec(substituted);\n if (ecrMatch) {\n return { kind: 'ecr', uri: substituted, account: ecrMatch[1]!, region: ecrMatch[2]! };\n }\n\n return { kind: 'public', uri: substituted };\n}\n\n/**\n * When the flat string still contains `${X}` after substitution, check\n * whether `X` (or `X.<attr>`) names a same-stack `AWS::ECR::Repository`.\n * Returns the logical ID of the offending repo for a precise error\n * message; `undefined` otherwise.\n */\nfunction findUnresolvedEcrRepositoryRef(\n substituted: string,\n resources: Record<string, TemplateResource>\n): string | undefined {\n const placeholderRegex = /\\$\\{([^}]+)\\}/g;\n let m: RegExpExecArray | null;\n while ((m = placeholderRegex.exec(substituted)) !== null) {\n const key = m[1]!;\n if (key.startsWith('AWS::')) continue;\n const dot = key.indexOf('.');\n const lid = dot === -1 ? key : key.slice(0, dot);\n if (resources[lid]?.Type === 'AWS::ECR::Repository') return lid;\n }\n return undefined;\n}\n\n/**\n * Classify a fully-substituted image URI (Tier 1 / Tier 2 produced a\n * concrete string) into the `ResolvedEcsImage` shape. Splits out so the\n * `Fn::GetAtt` path can share the regex-match branches.\n */\nfunction classifyResolvedImage(uri: string): ResolvedEcsImage {\n if (uri.includes('cdk-hnb659fds-container-assets-')) {\n const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);\n const out: ResolvedEcsImage = { kind: 'cdk-asset' };\n if (hashMatch) out.assetHash = hashMatch[1]!;\n return out;\n }\n const ecrMatch = /^(\\d{12})\\.dkr\\.ecr\\.([^.]+)\\.amazonaws\\.com(?:\\.cn)?\\//.exec(uri);\n if (ecrMatch) {\n return { kind: 'ecr', uri, account: ecrMatch[1]!, region: ecrMatch[2]! };\n }\n return { kind: 'public', uri };\n}\n\n/**\n * Handle the discrete `Fn::GetAtt: [<Repo>, 'RepositoryUri']` /\n * `'<Repo>.RepositoryUri'` shape against state-recorded resources. CDK\n * occasionally emits this instead of `Fn::Sub` when the user writes\n * `ContainerImage.fromEcrRepository(repo, tag)` and the tag is a literal.\n * Returns the resolved URI string on hit, `undefined` on miss (the caller\n * falls through to `extractImageString` for `Fn::Sub` / `Fn::Join` / `Ref`).\n */\nfunction tryResolveImageGetAtt(\n raw: unknown,\n resources: Record<string, TemplateResource>,\n context: EcsImageResolutionContext | undefined\n): string | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const obj = raw as Record<string, unknown>;\n const arg = obj['Fn::GetAtt'];\n if (arg === undefined) return undefined;\n\n let logicalId: string | undefined;\n let attr: string | undefined;\n if (\n Array.isArray(arg) &&\n arg.length === 2 &&\n typeof arg[0] === 'string' &&\n typeof arg[1] === 'string'\n ) {\n logicalId = arg[0];\n attr = arg[1];\n } else if (typeof arg === 'string') {\n const dot = arg.indexOf('.');\n if (dot > 0 && dot < arg.length - 1) {\n logicalId = arg.slice(0, dot);\n attr = arg.slice(dot + 1);\n }\n }\n if (!logicalId || !attr) return undefined;\n\n const refResource = resources[logicalId];\n if (refResource?.Type !== 'AWS::ECR::Repository') return undefined;\n if (attr !== 'RepositoryUri' && attr !== 'Arn') return undefined;\n\n const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];\n if (typeof cached === 'string' && cached.length > 0) return cached;\n return undefined;\n}\n\nfunction extractImageString(value: unknown): string | undefined {\n if (typeof value === 'string' && value.length > 0) return value;\n if (value && typeof value === 'object') {\n const obj = value as Record<string, unknown>;\n const sub = obj['Fn::Sub'];\n if (typeof sub === 'string' && sub.length > 0) return sub;\n if (Array.isArray(sub) && typeof sub[0] === 'string') return sub[0];\n const join = obj['Fn::Join'];\n if (Array.isArray(join) && join.length === 2 && Array.isArray(join[1])) {\n const sep = typeof join[0] === 'string' ? join[0] : '';\n const parts = (join[1] as unknown[])\n .map((p) => (typeof p === 'string' ? p : extractImageString(p)))\n .filter((p): p is string => p !== undefined);\n if (parts.length === (join[1] as unknown[]).length) return parts.join(sep);\n }\n }\n return undefined;\n}\n\n// `tryResolveImageFnJoin` (plus supporting helpers `findEcrRepositoryRefInTree` /\n// `resolveImageIntrinsic` / `resolveImageIntrinsicAny`) and the\n// `FnJoinResolveOutcome` type were extracted to `intrinsic-image.ts` so\n// `lambda-resolver.ts` can reuse them for container Lambdas (`Code.ImageUri`).\n// Both call sites import the shared helper at the top of their respective\n// resolver. See issue #286 Gap 2 and PR #280 for the original ECS shape.\n\nfunction parseVolume(\n raw: unknown,\n idx: number,\n taskLogicalId: string,\n subContext?: SubstitutionContext\n): ResolvedEcsVolume {\n if (!raw || typeof raw !== 'object') {\n throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] is not an object.`);\n }\n const v = raw as Record<string, unknown>;\n const name = pickString(v['Name']);\n if (!name) {\n throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] has no Name.`);\n }\n\n if (v['EFSVolumeConfiguration']) {\n throw new EcsTaskResolutionError(\n `Task '${taskLogicalId}' Volumes[${idx}] '${name}' uses EFSVolumeConfiguration, which ${getEmbedConfig().cliName} run-task cannot proxy locally. ` +\n `Workaround: bind-mount a local directory at the same containerPath via Host: { SourcePath: '<local-path>' }, or override at runtime via --env-vars semantics for a Phase 2 follow-up.`\n );\n }\n if (v['FSxWindowsFileServerVolumeConfiguration']) {\n throw new EcsTaskResolutionError(\n `Task '${taskLogicalId}' Volumes[${idx}] '${name}' uses FSxWindowsFileServerVolumeConfiguration, which ${getEmbedConfig().cliName} run-task cannot proxy locally.`\n );\n }\n\n const dockerCfg = v['DockerVolumeConfiguration'];\n if (dockerCfg && typeof dockerCfg === 'object') {\n const d = dockerCfg as Record<string, unknown>;\n const scope = pickString(d['Scope']) === 'shared' ? 'shared' : 'task';\n const cfg: ResolvedEcsVolume['dockerVolumeConfig'] = { scope };\n if (typeof d['Autoprovision'] === 'boolean') cfg.autoprovision = d['Autoprovision'];\n const driver = pickString(d['Driver']);\n if (driver) cfg.driver = driver;\n if (d['DriverOpts'] && typeof d['DriverOpts'] === 'object') {\n const opts: Record<string, string> = {};\n for (const [k, val] of Object.entries(d['DriverOpts'] as Record<string, unknown>)) {\n if (typeof val === 'string') opts[k] = val;\n }\n cfg.driverOpts = opts;\n }\n if (d['Labels'] && typeof d['Labels'] === 'object') {\n const labels: Record<string, string> = {};\n for (const [k, val] of Object.entries(d['Labels'] as Record<string, unknown>)) {\n if (typeof val === 'string') labels[k] = val;\n }\n cfg.labels = labels;\n }\n return { name, kind: 'docker', dockerVolumeConfig: cfg };\n }\n\n // Host bind mount (or anonymous when SourcePath unset).\n // SourcePath may be a flat string OR a CFn intrinsic (`Fn::Sub` /\n // `Fn::Join` / `Ref`) — CDK emits the intrinsic shape when the user\n // builds the path via `cdk.Fn.sub(...)` / `cdk.Fn.join(...)` (verified\n // via real `cdk synth` against `new ecs.Ec2TaskDefinition({ volumes:\n // [{ name, host: { sourcePath: cdk.Fn.sub('...') } }] })`). When state\n // + pseudo parameters are available (`--from-state` flow on\n // `cdkl run-task` already populates the context for env / secret\n // / image resolution), we substitute the intrinsic in place via the\n // same `substituteAgainstState` helper. Unresolvable intrinsics\n // hard-error — a Host bind mount with a missing path is not safe to\n // proxy as an anonymous volume (the container would silently see an\n // empty mount instead of the data it expects). Closes Gap 6 of #286.\n const host = v['Host'];\n if (host && typeof host === 'object') {\n const rawSourcePath = (host as Record<string, unknown>)['SourcePath'];\n const sourcePath = resolveVolumeSourcePath(rawSourcePath, name, idx, taskLogicalId, subContext);\n if (sourcePath !== undefined) {\n const abs = isAbsolute(sourcePath) ? sourcePath : resolve(process.cwd(), sourcePath);\n return { name, kind: 'host', hostPath: abs };\n }\n }\n return { name, kind: 'host' };\n}\n\n/**\n * Resolve a volume `Host.SourcePath` value to a flat host-path string.\n * Handles flat strings (pre-PR behavior) and the CFn intrinsic shapes\n * (`Fn::Sub` / `Fn::Join` / `Ref` / `Fn::GetAtt`) that CDK emits when\n * the user builds the path via `cdk.Fn.sub(...)` / `cdk.Fn.join(...)`.\n * Returns `undefined` when the field is absent or empty (anonymous\n * volume — the legacy path); throws `EcsTaskResolutionError` when the\n * intrinsic cannot be resolved against the supplied context.\n *\n * The hard-error on unresolvable intrinsics matches the upstream\n * pre-Gap-6 behavior (which silently dropped the SourcePath and produced\n * an anonymous volume) only on the failure mode — but now with a precise\n * actionable message instead of mysteriously empty mounts at runtime.\n */\nfunction resolveVolumeSourcePath(\n raw: unknown,\n volumeName: string,\n idx: number,\n taskLogicalId: string,\n subContext: SubstitutionContext | undefined\n): string | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (typeof raw === 'string') return raw.length > 0 ? raw : undefined;\n if (typeof raw !== 'object') {\n throw new EcsTaskResolutionError(\n `Task '${taskLogicalId}' Volumes[${idx}] '${volumeName}' Host.SourcePath must be a string or a CFn intrinsic, got ${typeof raw}.`\n );\n }\n if (!subContext) {\n throw new EcsTaskResolutionError(\n `Task '${taskLogicalId}' Volumes[${idx}] '${volumeName}' Host.SourcePath is a CFn intrinsic (Ref/Fn::Sub/Fn::Join/Fn::GetAtt). ` +\n `Pass --from-state to substitute it against the deployed state + AWS pseudo parameters.`\n );\n }\n const sub = substituteAgainstState(raw, subContext);\n if (sub.kind === 'literal' && typeof sub.value === 'string' && sub.value.length > 0) {\n return sub.value;\n }\n throw new EcsTaskResolutionError(\n `Task '${taskLogicalId}' Volumes[${idx}] '${volumeName}' Host.SourcePath could not be resolved: ` +\n (sub.kind === 'literal' ? 'resolved to non-string / empty value' : sub.reason)\n );\n}\n\n/**\n * Synth-time placeholder marker used in the account-id segment of an ARN\n * when the role's account cannot be known statically (the role is defined\n * inline as an `AWS::IAM::Role` in the same stack). The CLI replaces this\n * with the live account via STS `GetCallerIdentity` lazily — only when\n * `--assume-task-role` is used in its bare form and the resolved ARN\n * still contains this marker.\n */\nexport const TASK_ROLE_ACCOUNT_PLACEHOLDER = '${AWS::AccountId}';\n\n/**\n * Resolve a Task / Execution role ARN reference. Accepts:\n *\n * - A flat string ARN — returned verbatim.\n * - `Ref` / `Fn::GetAtt[..., 'Arn']` against an `AWS::IAM::Role` in\n * the same stack — returns a synth-time placeholder ARN of the shape\n * `arn:aws:iam::${AWS::AccountId}:role/<RoleLogicalId>` (the CLI\n * fills the account segment in lazily via STS when the bare\n * `--assume-task-role` form is used).\n * - `Fn::Join` / `Fn::Sub` — when the supplied substitution context\n * can resolve every nested intrinsic to a literal (state + pseudo\n * parameters via `--from-state` flow), the resulting flat ARN is\n * returned. This covers the rare `iam.Role.fromRoleArn(scope, id,\n * cdk.Fn.join(...))` pattern verified via real `cdk synth` on\n * 2026-05-12 against `iam.Role.fromRoleArn(stack, 'X', Fn.join(':',\n * ['arn','aws','iam','',cdk.Aws.ACCOUNT_ID,'role/Name']))`.\n * Closes Gap 5 of #286.\n *\n * Returns `undefined` when the reference cannot be resolved (logical id\n * missing, non-IAM-Role target, unsupported intrinsic shape, or\n * intrinsic shape whose nested refs cannot be resolved without\n * `--from-state`).\n */\nfunction resolveRoleArn(\n value: unknown,\n resources: Record<string, TemplateResource>,\n subContext?: SubstitutionContext\n): string | undefined {\n if (value === undefined || value === null) return undefined;\n if (typeof value === 'string') return value;\n if (typeof value !== 'object') return undefined;\n const obj = value as Record<string, unknown>;\n\n let refLogicalId: string | undefined;\n if ('Ref' in obj && typeof obj['Ref'] === 'string') {\n refLogicalId = obj['Ref'];\n } else if ('Fn::GetAtt' in obj) {\n const arg = obj['Fn::GetAtt'];\n if (Array.isArray(arg) && typeof arg[0] === 'string') {\n // Accept `[<LogicalId>, 'Arn']`; other attribute names (rare on IAM\n // Role refs) fall through to undefined since the placeholder we emit\n // is always the role ARN shape.\n const attr = typeof arg[1] === 'string' ? arg[1] : '';\n if (attr === '' || attr === 'Arn') refLogicalId = arg[0];\n }\n }\n if (refLogicalId !== undefined) {\n const role = resources[refLogicalId];\n if (role?.Type !== 'AWS::IAM::Role') return undefined;\n return `arn:aws:iam::${TASK_ROLE_ACCOUNT_PLACEHOLDER}:role/${refLogicalId}`;\n }\n\n // `Fn::Join` / `Fn::Sub` — `iam.Role.fromRoleArn(stack, id,\n // cdk.Fn.join(...))` shape. Resolve via state + pseudo parameters if\n // available; fall back to undefined (silent-drop, pre-PR behavior)\n // when the context can't fully resolve the intrinsic — same posture\n // as the existing Ref-to-missing-resource path.\n if ('Fn::Join' in obj || 'Fn::Sub' in obj) {\n if (!subContext) return undefined;\n const sub = substituteAgainstState(value, subContext);\n if (sub.kind === 'literal' && typeof sub.value === 'string' && sub.value.length > 0) {\n return sub.value;\n }\n return undefined;\n }\n\n return undefined;\n}\n\nfunction pickString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\nfunction pickStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const out: string[] = [];\n for (const v of value) {\n if (typeof v === 'string') out.push(v);\n }\n return out;\n}\n\nfunction notFoundError(\n target: string,\n stack: StackInfo,\n resources: Record<string, TemplateResource>\n): EcsTaskResolutionError {\n const tasks: { displayPath: string; logicalId: string }[] = [];\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ECS::TaskDefinition') continue;\n const meta = resource.Metadata;\n const cdkPath = typeof meta?.['aws:cdk:path'] === 'string' ? meta['aws:cdk:path'] : '';\n tasks.push({ displayPath: cdkPath || logicalId, logicalId });\n }\n let msg = `target '${target}' did not match any ECS task definition in ${stack.stackName}.\\n\\n`;\n if (tasks.length === 0) {\n msg += `Stack ${stack.stackName} has no AWS::ECS::TaskDefinition resources.`;\n } else {\n const width = Math.max(...tasks.map((t) => t.displayPath.length));\n msg += `Available task definitions in ${stack.stackName}:\\n`;\n for (const t of tasks) {\n msg += ` ${t.displayPath.padEnd(width)} (${t.logicalId})\\n`;\n }\n }\n return new EcsTaskResolutionError(msg.trimEnd());\n}\n\n/**\n * Resolve a `kind: 'cdk-asset'` Image entry back to the on-disk build\n * context recorded in the stack's asset manifest. Surfaces an absolute\n * path to the cdk.out asset directory + the dockerfile name so the\n * runner can hand the pair to `buildDockerImage` directly. Returns\n * `undefined` when the asset isn't in the manifest — the caller hard-\n * errors with a clear \"re-synthesize\" pointer.\n */\nexport function buildCdkOutDir(stack: StackInfo): string | undefined {\n if (!stack.assetManifestPath) return undefined;\n return dirname(stack.assetManifestPath);\n}\n\n/**\n * Verify that a directory referenced by a docker-volume `Host.SourcePath`\n * actually exists. Surfaced as a warning rather than a hard error so\n * users can bind-mount future-created paths.\n */\nexport function checkVolumeHostPath(hostPath: string): boolean {\n try {\n return existsSync(hostPath) && statSync(hostPath).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Async post-pass that walks the resolved task's container Environment +\n * Secrets entries from the raw template (preserved in `task.resource`)\n * and re-attempts substitution via {@link substituteAgainstStateAsync}\n * against the supplied context. The sync `parseContainerDefinition`\n * pass already substituted everything the legacy resolver handles; this\n * pass picks up the additional shapes the async resolver supports —\n * specifically `Fn::ImportValue` / `Fn::GetStackOutput` (issue #454).\n *\n * Resolved entries are patched onto the container's `environment` /\n * `secrets` map AND the corresponding `warnings` entries are filtered\n * out so the CLI doesn't print a stale per-container warn for an entry\n * the post-pass successfully resolved. Entries that STILL can't resolve\n * (e.g. producer stack not deployed) keep their original warning so the\n * CLI's UX matches the sync path.\n *\n * Pure-functional on the task structure outside of in-place mutation of\n * the container's `environment` / `secrets` / `warnings` arrays. The\n * task is expected to be the same `ResolvedEcsTask` instance returned\n * by `resolveEcsTaskTarget` — the runner downstream reads from these\n * fields directly.\n */\nexport async function applyCrossStackResolverToTask(\n task: ResolvedEcsTask,\n context: SubstitutionContext\n): Promise<void> {\n if (!context.crossStackResolver) return;\n const props = task.resource.Properties ?? {};\n const rawContainers = props['ContainerDefinitions'];\n if (!Array.isArray(rawContainers)) return;\n\n for (let idx = 0; idx < task.containers.length; idx += 1) {\n const container = task.containers[idx]!;\n const raw = rawContainers[idx];\n if (!raw || typeof raw !== 'object') continue;\n const c = raw as Record<string, unknown>;\n const containerName = pickString(c['Name']) ?? container.name;\n\n // Track keys the post-pass resolved so we can filter the matching\n // entries out of `container.warnings` (which the sync pass populated\n // with \"dropped: <reason>\" lines).\n const resolvedEnvKeys = new Set<string>();\n const resolvedSecretNames = new Set<string>();\n\n // Environment[].Value\n if (Array.isArray(c['Environment'])) {\n for (const entry of c['Environment'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const e = entry as Record<string, unknown>;\n const key = pickString(e['Name']);\n const value = e['Value'];\n if (!key) continue;\n // Skip literal values + entries already resolved by the sync\n // pass (present in `container.environment`).\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {\n continue;\n }\n if (key in container.environment) continue;\n if (!isCrossStackIntrinsic(value)) {\n // The sync pass already tried this — re-trying here can't\n // produce a different outcome.\n continue;\n }\n const { result: sub, consumedSensitive } = await resolveWithSensitivityAsync(\n value,\n context\n );\n if (sub.kind === 'literal') {\n container.environment[key] = String(sub.value);\n resolvedEnvKeys.add(key);\n // SecureString-backed value — keep it off the `docker run` argv (#99).\n if (consumedSensitive && !container.sensitiveEnvKeys.includes(key)) {\n container.sensitiveEnvKeys.push(key);\n }\n }\n }\n }\n\n // Secrets[].ValueFrom\n if (Array.isArray(c['Secrets'])) {\n for (const entry of c['Secrets'] as unknown[]) {\n if (!entry || typeof entry !== 'object') continue;\n const e = entry as Record<string, unknown>;\n const sName = pickString(e['Name']);\n const valueFromRaw = e['ValueFrom'];\n if (!sName) continue;\n if (typeof valueFromRaw === 'string' && valueFromRaw.length > 0) continue;\n if (container.secrets.some((s) => s.name === sName)) continue;\n if (!isCrossStackIntrinsic(valueFromRaw)) continue;\n const sub = await substituteAgainstStateAsync(valueFromRaw, context);\n if (sub.kind === 'literal' && typeof sub.value === 'string' && sub.value.length > 0) {\n container.secrets.push({ name: sName, valueFrom: sub.value });\n resolvedSecretNames.add(sName);\n }\n }\n }\n\n if (resolvedEnvKeys.size > 0 || resolvedSecretNames.size > 0) {\n container.warnings = container.warnings.filter((w) => {\n for (const k of resolvedEnvKeys) {\n if (w.startsWith(`Environment '${k}' dropped:`)) return false;\n }\n for (const k of resolvedSecretNames) {\n if (w.startsWith(`Secret '${k}' dropped:`)) return false;\n }\n return true;\n });\n // Same filter at the task-level (the per-container warnings are\n // re-emitted onto `task.warnings` by `parseEcsTaskFromCdkResource`,\n // prefixed with `Container '<name>': `).\n task.warnings = task.warnings.filter((w) => {\n for (const k of resolvedEnvKeys) {\n if (w.startsWith(`Container '${containerName}': Environment '${k}' dropped:`)) {\n return false;\n }\n }\n for (const k of resolvedSecretNames) {\n if (w.startsWith(`Container '${containerName}': Secret '${k}' dropped:`)) {\n return false;\n }\n }\n return true;\n });\n }\n }\n}\n","/**\n * Map a CloudFormation `Runtime` string to the AWS Lambda base image that\n * bundles the matching runtime + the Lambda Runtime Interface Emulator (RIE),\n * plus the source-file extension for inline-code materialization.\n *\n * Per D1 in the issue, cdk-local uses the **full** base image\n * (`public.ecr.aws/lambda/<lang>:<version>`, ~600MB) over SAM's lighter\n * `public.ecr.aws/sam/emulation-<lang>` (~150MB). The size cost is one-time\n * per machine; in exchange the local runtime is the same artifact AWS runs\n * for container Lambdas, so a \"works locally, breaks in AWS\" mismatch is\n * almost always a config issue rather than an image divergence.\n *\n * Supports every current AWS Lambda runtime — Node.js, Python, Ruby,\n * Java, .NET, and the OS-only `provided.al2` / `provided.al2023` (the\n * canonical hosts for Go via a `bootstrap` binary, Rust, C/C++, or any\n * other compiled native runtime). The deprecated `go1.x` runtime is\n * explicitly rejected with a migration pointer to `provided.al2023`.\n *\n * Truly unknown runtime strings (e.g. typos like `nodejs99.x`, or\n * back-revs AWS retired well before cdk-local existed) fall through to a\n * generic error that lists every supported runtime.\n *\n * Ruby uses the same `<file>.<func>` handler grammar as Node.js and Python,\n * so the inline-code materializer's `lastIndexOf('.')` parse works\n * unchanged; only the file extension (`.rb`) differs.\n *\n * Java, .NET, and `provided.*` are **asset-backed only** — the Handler\n * value names a compiled artifact (`package.Class::method` for Java's\n * JVM class on the classpath; `Assembly::Namespace.Class::Method` for\n * .NET's CLR assembly / DLL; an arbitrary identifier per the user's\n * `bootstrap` binary for `provided.*`), which can only be supplied as a\n * compiled artifact directory packaged via `lambda.Code.fromAsset(...)`.\n * Inline `Code.ZipFile` has no meaning for any of them, so\n * `fileExtension` is `null` for every Java / .NET / `provided.*` entry\n * and `resolveRuntimeFileExtension` throws a runtime-specific message\n * routing the user to `Code.fromAsset(...)`.\n */\n\nimport { getEmbedConfig } from './embed-config.js';\n\ninterface RuntimeSpec {\n /** ECR image tag the container should pull. */\n readonly image: string;\n /**\n * Source-file extension (with leading dot) for inline-code\n * materialization (`Code.ZipFile`). Node.js → `.js`, Python → `.py`,\n * Ruby → `.rb`. `null` for runtimes whose Handler shape cannot be\n * satisfied by a single inline source file (Java needs a compiled\n * class hierarchy / JAR) — `resolveRuntimeFileExtension` rejects with\n * a routing message when callers hit this case.\n */\n readonly fileExtension: string | null;\n}\n\nconst SUPPORTED_RUNTIMES: Readonly<Record<string, RuntimeSpec>> = {\n 'nodejs18.x': { image: 'public.ecr.aws/lambda/nodejs:18', fileExtension: '.js' },\n 'nodejs20.x': { image: 'public.ecr.aws/lambda/nodejs:20', fileExtension: '.js' },\n 'nodejs22.x': { image: 'public.ecr.aws/lambda/nodejs:22', fileExtension: '.js' },\n 'nodejs24.x': { image: 'public.ecr.aws/lambda/nodejs:24', fileExtension: '.js' },\n 'python3.11': { image: 'public.ecr.aws/lambda/python:3.11', fileExtension: '.py' },\n 'python3.12': { image: 'public.ecr.aws/lambda/python:3.12', fileExtension: '.py' },\n 'python3.13': { image: 'public.ecr.aws/lambda/python:3.13', fileExtension: '.py' },\n 'python3.14': { image: 'public.ecr.aws/lambda/python:3.14', fileExtension: '.py' },\n 'ruby3.2': { image: 'public.ecr.aws/lambda/ruby:3.2', fileExtension: '.rb' },\n 'ruby3.3': { image: 'public.ecr.aws/lambda/ruby:3.3', fileExtension: '.rb' },\n 'java8.al2': { image: 'public.ecr.aws/lambda/java:8.al2', fileExtension: null },\n java11: { image: 'public.ecr.aws/lambda/java:11', fileExtension: null },\n java17: { image: 'public.ecr.aws/lambda/java:17', fileExtension: null },\n java21: { image: 'public.ecr.aws/lambda/java:21', fileExtension: null },\n dotnet6: { image: 'public.ecr.aws/lambda/dotnet:6', fileExtension: null },\n dotnet8: { image: 'public.ecr.aws/lambda/dotnet:8', fileExtension: null },\n 'provided.al2': { image: 'public.ecr.aws/lambda/provided:al2', fileExtension: null },\n 'provided.al2023': { image: 'public.ecr.aws/lambda/provided:al2023', fileExtension: null },\n};\n\nexport class UnsupportedRuntimeError extends Error {\n public readonly runtime: string;\n\n constructor(runtime: string, message: string) {\n super(message);\n this.runtime = runtime;\n this.name = 'UnsupportedRuntimeError';\n Object.setPrototypeOf(this, UnsupportedRuntimeError.prototype);\n }\n}\n\n/**\n * Resolve a Lambda `Runtime` value to the local-invoke base image tag.\n *\n * Throws {@link UnsupportedRuntimeError} for runtimes outside the supported\n * set (currently every AWS Lambda runtime — see {@link SUPPORTED_RUNTIMES}).\n * Container Lambdas (`Code.ImageUri`, no `Runtime` property) are handled\n * separately and never reach this function.\n */\nexport function resolveRuntimeImage(runtime: string): string {\n return resolveRuntimeSpec(runtime).image;\n}\n\n/**\n * Resolve a Lambda `Runtime` value to the source-file extension used when\n * materializing an inline `Code.ZipFile` body to disk. Node.js → `.js`,\n * Python → `.py`, Ruby → `.rb`. Throws {@link UnsupportedRuntimeError} on\n * the same runtime set as {@link resolveRuntimeImage}.\n *\n * Additionally throws when the resolved runtime has `fileExtension: null`\n * — this is the canonical entry point for the inline-`Code.ZipFile` branch\n * in both `cdkl invoke` and `cdkl start-api`, so rejecting\n * here surfaces a user-friendly \"use Code.fromAsset\" message before the\n * callers reach the materializer. Asset-backed Lambdas never call this.\n */\nexport function resolveRuntimeFileExtension(runtime: string): string {\n const spec = resolveRuntimeSpec(runtime);\n if (spec.fileExtension === null) {\n throw new UnsupportedRuntimeError(\n runtime,\n `Inline 'Code.ZipFile' is not supported for runtime '${runtime}'. ` +\n 'The Lambda Handler shape for this runtime names a compiled artifact (a JVM class, a .NET assembly, or — for the OS-only `provided.*` runtimes — an arbitrary `bootstrap` binary) that cannot be expressed as a single inline source file. ' +\n 'Use `lambda.Code.fromAsset(<dir>)` with a directory containing the compiled output (.class hierarchy / JAR / DLL / native binary).'\n );\n }\n return spec.fileExtension;\n}\n\n/**\n * Resolve a Lambda `Runtime` value to its full {@link RuntimeSpec}. Public\n * for callers that need both the image AND the file extension in one step;\n * the named helpers above wrap this for the common single-field cases.\n */\nexport function resolveRuntimeSpec(runtime: string): RuntimeSpec {\n if (typeof runtime !== 'string' || runtime.length === 0) {\n throw new UnsupportedRuntimeError(\n String(runtime),\n 'Lambda function has no Runtime property. This branch is only reached for ZIP Lambdas; container-image Lambdas (Code.ImageUri) take a different code path that does not consult the Runtime property.'\n );\n }\n\n const spec = SUPPORTED_RUNTIMES[runtime];\n if (spec) return spec;\n\n // The pre-OAL `go1.x` runtime was end-of-lifed by AWS Lambda on\n // 2024-01-08 and no longer has a base image at `public.ecr.aws/lambda/`.\n // Reject with a migration pointer rather than the generic \"unknown\n // runtime\" message — users who still have `lambda.Runtime.GO_1_X` in\n // their CDK code will hit this and need the explicit next step.\n if (runtime === 'go1.x') {\n throw new UnsupportedRuntimeError(\n runtime,\n `Runtime 'go1.x' was deprecated by AWS Lambda on 2024-01-08 and is no longer available. ` +\n 'Migrate to the OS-only runtime: build your Go program as a `bootstrap` binary and set the CDK runtime to `lambda.Runtime.PROVIDED_AL2023` (or `lambda.Runtime.PROVIDED_AL2`). ' +\n 'See https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html'\n );\n }\n\n throw new UnsupportedRuntimeError(\n runtime,\n `Unknown runtime '${runtime}'. ${getEmbedConfig().cliName} invoke supports nodejs18.x / nodejs20.x / nodejs22.x / nodejs24.x / python3.11 / python3.12 / python3.13 / python3.14 / ruby3.2 / ruby3.3 / java8.al2 / java11 / java17 / java21 / dotnet6 / dotnet8 / provided.al2 / provided.al2023.`\n );\n}\n\n/**\n * Whether the runtime is in the supported set. Useful for callers that\n * want to filter without catching an exception.\n */\nexport function isSupportedRuntime(runtime: string): boolean {\n return runtime in SUPPORTED_RUNTIMES;\n}\n\n/**\n * In-container path where cdk-local should bind-mount the function's\n * deployment package (asset directory or materialized inline tmpdir).\n *\n * - Most runtimes: `/var/task` — the standard Lambda deployment dir.\n * The runtime-specific entrypoint (Node / Python / Ruby / Java / .NET)\n * loads the user code from this path.\n * - `provided.al2` / `provided.al2023`: `/var/runtime` — the AWS Lambda\n * `provided.*` base images hardcode `/lambda-entrypoint.sh` to\n * `exec /usr/local/bin/aws-lambda-rie /var/runtime/bootstrap`, so the\n * user's `bootstrap` binary must live at `/var/runtime/bootstrap` and\n * NOT under `/var/task`. AWS Lambda's runtime service ordinarily\n * stages the bootstrap to `/var/runtime` as part of init; RIE inside\n * the public base image does not, so the host-side bind mount has to\n * target `/var/runtime` directly.\n *\n * Throws {@link UnsupportedRuntimeError} for the same runtime set as\n * the other resolvers — by the time a caller asks for the mount path\n * the runtime has already been validated.\n */\nexport function resolveRuntimeCodeMountPath(runtime: string): string {\n // Trigger the standard validation so unknown runtimes throw the same\n // message users see from resolveRuntimeImage.\n resolveRuntimeSpec(runtime);\n if (runtime === 'provided.al2' || runtime === 'provided.al2023') {\n return '/var/runtime';\n }\n return '/var/task';\n}\n","import { execFile, spawn } from 'node:child_process';\nimport { createServer } from 'node:net';\nimport { promisify } from 'node:util';\nimport { getDockerCmd, runDockerForeground, runDockerStreaming } from '../utils/docker-cmd.js';\nimport { getLogger } from '../utils/logger.js';\nimport { getEmbedConfig } from './embed-config.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Wraps `docker pull` / `docker run` / `docker rm` for `cdkl invoke`.\n *\n * Mirrors the style of `src/assets/docker-asset-publisher.ts` (execFile for\n * one-shot calls, spawn for long-running ones). Kept as a separate file so\n * the command layer's wiring stays small; PR 5 (container Lambda) is\n * expected to add a second non-build call site, at which point the\n * common surface can be lifted into a shared helper.\n */\n\nexport class DockerRunnerError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DockerRunnerError';\n Object.setPrototypeOf(this, DockerRunnerError.prototype);\n }\n}\n\nexport interface DockerRunOptions {\n /** Image to run (e.g. `public.ecr.aws/lambda/nodejs:20`). */\n image: string;\n /**\n * Bind mounts: `[hostPath, containerPath]` pairs. cdk-local uses this to\n * expose the function's local code at `/var/task` (read-only). Empty\n * for container Lambdas (PR 5) — the image already has the code at\n * `/var/task`, no host bind-mount needed.\n */\n mounts: { hostPath: string; containerPath: string; readOnly?: boolean }[];\n /**\n * Additional bind mounts applied AFTER `mounts` (PR 6 of #224, issue\n * #232 — Lambda Layers). The split is purely organizational: it lets\n * the call site keep \"the function's own code\" (`mounts`) separate\n * from any extra mounts the caller wants to compose. The docker-runner\n * emits one `-v <hostPath>:<containerPath>:<ro?>` per entry, in\n * order, with NO target-path coalescing — Docker rejects duplicate\n * targets (`Error response from daemon: Duplicate mount point: ...`),\n * so the caller MUST ensure each entry's `containerPath` is unique\n * across `mounts` + `extraMounts`. For Lambda Layers specifically:\n * AWS's \"last layer wins on file collision\" semantic is realized by\n * the caller (`materializeLambdaLayers` in `local-invoke.ts` /\n * `local-start-api.ts`) `cpSync`-merging every layer's asset\n * directory into ONE host tmpdir in template order, then passing a\n * single `{hostPath: <tmpdir>, containerPath: '/opt'}` entry here —\n * NOT one mount per layer.\n */\n extraMounts?: { hostPath: string; containerPath: string; readOnly?: boolean }[];\n /** Environment variables to forward into the container. */\n env: Record<string, string>;\n /**\n * Extra env keys (beyond {@link SENSITIVE_ENV_KEYS}) whose VALUES are\n * sensitive and must be routed through docker's value-from-process-env\n * form (`-e KEY`) so they never appear on the `docker run` argv. Today\n * this carries the env keys that resolved to a decrypted `SecureString`\n * SSM parameter under `--from-cfn-stack` (issue #99). Unioned with\n * {@link SENSITIVE_ENV_KEYS} before {@link appendEnvFlags}.\n */\n sensitiveEnvKeys?: ReadonlySet<string>;\n /**\n * Container CMD. For ZIP Lambda base images this is the handler string,\n * e.g. `index.handler`. For container Lambdas (PR 5) this is the\n * `ImageConfig.Command` array — passed verbatim, may be empty when the\n * image's own CMD is sufficient.\n */\n cmd: string[];\n /** Host port to bind the container port ({@link containerPort}) to. */\n hostPort: number;\n /**\n * Container port to publish {@link hostPort} to. Defaults to `8080` (the\n * Lambda RIE port + the AgentCore HTTP contract port). `cdkl\n * invoke-agentcore` overrides it to `8000` for an MCP-protocol runtime,\n * whose container serves `POST /mcp` on `0.0.0.0:8000`.\n */\n containerPort?: number;\n /** Host to bind to (default `127.0.0.1`). */\n host?: string;\n /**\n * Optional Node.js inspector port. When set the container also publishes\n * `<port>:<port>` and the caller is expected to have set\n * `NODE_OPTIONS=--inspect-brk=0.0.0.0:<port>` in `env`.\n */\n debugPort?: number;\n /**\n * `--platform <linux/amd64|linux/arm64>`. PR 5: container Lambdas\n * declare `Architectures`, and the run-time platform must match the\n * built image. Without this an arm64 host running an x86_64 Lambda\n * hits emulation (slow) and an x86_64 host running arm64 fails with\n * `exec format error`. Omitted when undefined (the ZIP path on the\n * host's default arch is the original behavior).\n */\n platform?: string;\n /**\n * `--entrypoint <first>` for container Lambdas (PR 5,\n * `ImageConfig.EntryPoint`). When set, only the first entry is passed\n * to docker as `--entrypoint` (docker accepts a single binary there);\n * the remaining entries are pre-pended to `cmd` as positional args.\n * Most container Lambdas leave EntryPoint unset so `/lambda-entrypoint.sh`\n * stays in charge of dispatching to RIE.\n */\n entryPoint?: string[];\n /** `--workdir <dir>` for container Lambdas (PR 5, `ImageConfig.WorkingDirectory`). */\n workingDir?: string;\n /**\n * Optional `--name` for the container. `cdkl start-api` sets a\n * stable `cdkl-<logicalId>-<pid>-<rand>` name so the verify.sh\n * trap can sweep orphans (`docker ps --filter name=cdkl-`)\n * regardless of how the server exited. `cdkl invoke` leaves it\n * unset and lets docker auto-assign — short-lived containers don't\n * benefit from a stable name.\n */\n name?: string;\n /**\n * Optional `--network <name>` for the container. `cdkl run-task`\n * uses this so every container in a task lands on the same per-task\n * docker network and can reach the metadata-endpoints sidecar at\n * `169.254.170.2`. Unset → docker's default bridge network (current\n * behavior for `cdkl invoke` / `cdkl start-api`).\n */\n network?: string;\n /**\n * Optional sized tmpfs mount (issue #440 — Lambda\n * `Properties.EphemeralStorage.Size`). When set, emits\n * `--tmpfs <target>:rw,size=<sizeMb>m` so the container's `<target>`\n * (typically `/tmp`) is a memory-backed filesystem capped at\n * `sizeMb` MiB. Handlers that exceed the cap fail with `ENOSPC`\n * the way they would on AWS, and handlers that detect free space\n * via `statvfs` / `df` see the configured cap rather than the\n * host's overlay-fs.\n *\n * Unset → no `--tmpfs` flag, the container's `/tmp` is whatever\n * the base image provides (preserves the pre-#440 behavior for\n * Lambdas without `EphemeralStorage`).\n */\n tmpfs?: { target: string; sizeMb: number };\n /**\n * Extra `--add-host <host>:<ip>` mappings written into the\n * container's `/etc/hosts`. Used by `cdkl start-api`'s\n * WebSocket support (#462) to add\n * `host.docker.internal:host-gateway` so the Lambda container can\n * reach the host's `@connections` HTTP endpoint regardless of OS\n * (Docker Desktop on macOS / Windows already resolves\n * `host.docker.internal`; Linux native dockerd needs the explicit\n * `host-gateway` flag since 20.10+).\n */\n extraHosts?: { host: string; ip: string }[];\n}\n\n/**\n * Pull the image. No-op when `skipPull` is true.\n *\n * In verbose mode (`--verbose` / global log level `debug`), streams the\n * full `docker pull` progress to stdout so the user sees per-layer\n * downloads. In the default compact mode the call is silent (cached\n * images are the common case; a fresh pull still shows progress only\n * via `--verbose`). Errors are always surfaced: the captured stderr is\n * folded into the thrown `DockerRunnerError` message.\n */\nexport async function pullImage(image: string, skipPull: boolean): Promise<void> {\n const logger = getLogger().child('docker');\n if (skipPull) {\n logger.debug(`Skipping docker pull for ${image} (--no-pull)`);\n return;\n }\n if (getLogger().getLevel() === 'debug') {\n logger.info(`Pulling ${image}...`);\n try {\n await runDockerForeground(['pull', image]);\n } catch (err) {\n const e = err as Error;\n throw new DockerRunnerError(`docker pull ${image} failed: ${e.message}`);\n }\n return;\n }\n logger.debug(`Pulling ${image} (silent — pass --verbose to stream progress)`);\n // Captured-mode pull: stdout/stderr collected, mirrored only when\n // --verbose is on (which the branch above already handles via\n // runDockerForeground), so streamLive: false keeps the silent\n // contract on the default compact log level.\n try {\n await runDockerStreaming(['pull', image], { streamLive: false });\n } catch (err) {\n const e = err as {\n exitCode?: number | null;\n stderr?: string;\n stdout?: string;\n message?: string;\n };\n // Distinguish spawn-level failure (ENOENT — docker binary missing,\n // surfaces with the helpful Install-Docker / CDK_DOCKER hint from\n // `spawnStreaming`'s ENOENT branch) from non-zero exit (genuine\n // pull failure with captured stderr). The ENOENT path rejects with\n // a plain `Error` (no `exitCode` field); the non-zero-exit path\n // rejects with `SpawnError` (`exitCode` + `stderr` + `stdout`).\n if (e.exitCode === undefined || e.exitCode === null) {\n throw new DockerRunnerError(`docker pull ${image} failed: ${e.message ?? String(err)}`);\n }\n const detail = e.stderr?.trim() || e.stdout?.trim() || '(no output)';\n throw new DockerRunnerError(`docker pull ${image} exited with code ${e.exitCode}: ${detail}`);\n }\n}\n\n/**\n * Run the container detached. Returns the container ID.\n *\n * The caller is responsible for:\n * - polling `host:port` for RIE readiness,\n * - issuing the invoke,\n * - calling `removeContainer` from a `try`/`finally` so the container\n * is cleaned up on any error path including SIGINT.\n */\nexport async function runDetached(opts: DockerRunOptions): Promise<string> {\n const args: string[] = ['run', '-d', '--rm'];\n\n if (opts.name) {\n args.push('--name', opts.name);\n }\n if (opts.network) {\n args.push('--network', opts.network);\n }\n if (opts.platform) {\n args.push('--platform', opts.platform);\n }\n if (opts.extraHosts) {\n for (const entry of opts.extraHosts) {\n args.push('--add-host', `${entry.host}:${entry.ip}`);\n }\n }\n\n const host = opts.host ?? '127.0.0.1';\n args.push('-p', `${host}:${opts.hostPort}:${opts.containerPort ?? 8080}`);\n if (opts.debugPort !== undefined) {\n args.push('-p', `${host}:${opts.debugPort}:${opts.debugPort}`);\n }\n\n for (const mount of opts.mounts) {\n const ro = mount.readOnly ? ':ro' : '';\n args.push('-v', `${mount.hostPath}:${mount.containerPath}${ro}`);\n }\n // PR 6 (#232): layer mounts are emitted after the function's own\n // mounts. Order within `extraMounts` is preserved — the caller (the\n // CLI's `resolveZipImagePlan`) feeds layers in the same order they\n // appear in `Properties.Layers`, so AWS's \"last layer wins on file\n // collision\" semantics hold.\n if (opts.extraMounts) {\n for (const mount of opts.extraMounts) {\n const ro = mount.readOnly ? ':ro' : '';\n args.push('-v', `${mount.hostPath}:${mount.containerPath}${ro}`);\n }\n }\n\n // AWS credentials (and any caller-flagged sensitive keys, e.g. decrypted\n // SecureString SSM values — issue #99) route through docker's\n // value-from-process-env form (`-e KEY`, value supplied via the spawned\n // process's env below) so they never appear in `docker run`'s argv.\n // Non-sensitive env keeps the inline `-e KEY=VALUE` form.\n const sensitiveKeys =\n opts.sensitiveEnvKeys && opts.sensitiveEnvKeys.size > 0\n ? new Set<string>([...SENSITIVE_ENV_KEYS, ...opts.sensitiveEnvKeys])\n : SENSITIVE_ENV_KEYS;\n const passthroughEnv = appendEnvFlags(args, opts.env, sensitiveKeys);\n\n // Issue #440 — Lambda EphemeralStorage.Size: emit `--tmpfs\n // <target>:rw,size=<N>m` so the container's `/tmp` is capped at the\n // templated value. Placed AFTER -e flags and BEFORE --workdir to\n // keep the order stable with the existing `-p` / `-v` / `-e`\n // mount-shaped block; downstream `--workdir` / `--entrypoint`\n // / image / cmd ordering is unchanged.\n if (opts.tmpfs) {\n args.push('--tmpfs', `${opts.tmpfs.target}:rw,size=${opts.tmpfs.sizeMb}m`);\n }\n\n if (opts.workingDir) {\n args.push('--workdir', opts.workingDir);\n }\n\n // ImageConfig.EntryPoint maps to `--entrypoint <first>` plus the rest\n // as positional args before CMD. Docker only accepts a single value\n // for `--entrypoint`; multi-arg entrypoints carry their tail through\n // the positional CMD slot. Most container Lambdas omit EntryPoint\n // entirely so `/lambda-entrypoint.sh` stays in charge of RIE dispatch.\n let entryPointTail: string[] = [];\n if (opts.entryPoint && opts.entryPoint.length > 0) {\n args.push('--entrypoint', opts.entryPoint[0]!);\n entryPointTail = opts.entryPoint.slice(1);\n }\n\n args.push(opts.image, ...entryPointTail, ...opts.cmd);\n\n const logger = getLogger().child('docker');\n logger.debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(' ')}`);\n\n try {\n const { stdout } = await execFileAsync(getDockerCmd(), args, {\n maxBuffer: 10 * 1024 * 1024,\n ...execEnvForSecrets(passthroughEnv),\n });\n return stdout.trim();\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n throw new DockerRunnerError(\n `docker run failed: ${err.stderr?.trim() || err.message || String(error)}`\n );\n }\n}\n\n/**\n * `docker logs -f <id>` plumbed to stdout/stderr. Returns a function that\n * stops the stream (used by the caller in a `finally` block).\n */\nexport function streamLogs(containerId: string): () => void {\n const proc = spawn(getDockerCmd(), ['logs', '-f', containerId], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n proc.stdout?.on('data', (chunk: Buffer) => process.stdout.write(chunk));\n proc.stderr?.on('data', (chunk: Buffer) => process.stderr.write(chunk));\n // Swallow the exit code; this child is just plumbing.\n proc.on('error', () => {\n /* the parent flow surfaces docker errors via runDetached / removeContainer */\n });\n return () => {\n if (!proc.killed) proc.kill('SIGTERM');\n };\n}\n\n/**\n * Best-effort `docker rm -f <id>`. Errors are swallowed (logged at debug)\n * because this typically runs from a `finally` and the parent has its own\n * error to surface.\n */\nexport async function removeContainer(containerId: string): Promise<void> {\n if (!containerId) return;\n const logger = getLogger().child('docker');\n try {\n await execFileAsync(getDockerCmd(), ['rm', '-f', containerId]);\n logger.debug(`Removed container ${containerId}`);\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n logger.debug(\n `docker rm -f ${containerId} failed: ${err.stderr || err.message || String(error)}`\n );\n }\n}\n\n/**\n * Verify the docker daemon is reachable. Surfaces a friendlier error than\n * the raw `ENOENT` / \"Cannot connect to the Docker daemon\" the user would\n * otherwise see at the first run call. Called once up front.\n */\nexport async function ensureDockerAvailable(): Promise<void> {\n const cmd = getDockerCmd();\n try {\n await execFileAsync(cmd, ['version', '--format', '{{.Server.Version}}']);\n } catch (error) {\n const err = error as { code?: string; stderr?: string; message?: string };\n if (err.code === 'ENOENT') {\n throw new DockerRunnerError(\n `${cmd} is not installed or not on PATH. ${getEmbedConfig().cliName} invoke needs Docker (or a compatible CLI specified via CDK_DOCKER) — install it and retry.`\n );\n }\n throw new DockerRunnerError(\n `${cmd} daemon is not reachable: ${err.stderr?.trim() || err.message || String(error)}. ` +\n 'Start Docker Desktop / the docker daemon and retry.'\n );\n }\n}\n\n/**\n * Allocate a free TCP port on `127.0.0.1`. Used to pick a host port for\n * publishing the RIE :8080 endpoint without colliding with whatever else\n * the user has running. The OS assigns a port via `port: 0` and we\n * close the probe before returning so docker can bind it next.\n *\n * There is a tiny race window between close and `docker run -p` — in\n * practice it's never been observed for local invoke; if it ever\n * surfaces, the caller can retry with a fresh port.\n */\nexport function pickFreePort(): Promise<number> {\n return new Promise<number>((resolvePort, rejectPort) => {\n const server = createServer();\n server.unref();\n server.on('error', rejectPort);\n server.listen(0, '127.0.0.1', () => {\n const address = server.address();\n if (!address || typeof address === 'string') {\n server.close();\n rejectPort(new Error('Could not allocate a host port'));\n return;\n }\n const port = address.port;\n server.close(() => resolvePort(port));\n });\n });\n}\n\n/**\n * Env keys whose VALUES are sensitive (AWS credentials). These are\n * routed through docker's value-from-process-env form by\n * {@link appendEnvFlags} so the value never appears in `docker run`'s\n * argv (`ps` / `/proc/<pid>/cmdline`), and they are also redacted by\n * {@link redactAwsCredentialsInArgs} as a defense for any path that\n * still emits the inline `-e <KEY>=<value>` form into the debug log.\n */\nexport const SENSITIVE_ENV_KEYS: ReadonlySet<string> = new Set([\n 'AWS_ACCESS_KEY_ID',\n 'AWS_SECRET_ACCESS_KEY',\n 'AWS_SESSION_TOKEN',\n]);\n\n/**\n * Append `-e` flags for `env` to `args`, routing keys in `sensitiveKeys`\n * through docker's value-from-process-env form (`-e KEY`, with NO\n * `=value`). Docker reads the value from its OWN environment at run time,\n * so the secret never lands in `docker run`'s argv — invisible to\n * `ps aux` / `/proc/<pid>/cmdline` (other users on a shared host) and to\n * shell history. Non-sensitive keys keep the inline `-e KEY=VALUE` form\n * (fine for non-secret config, and keeps `--debug` output readable).\n *\n * Returns the `{ KEY: value }` map of routed-through keys; the caller MUST\n * merge it into the spawned docker process's `env` (see\n * {@link execEnvForSecrets}) so docker can resolve each passed-through\n * key. Values still appear in `docker inspect` Config.Env — that is\n * inherent to container env and matches production behavior.\n *\n * Unlike `--env-file`, this handles multi-line values (e.g. PEM secrets)\n * and needs no temp file on disk.\n */\nexport function appendEnvFlags(\n args: string[],\n env: Record<string, string>,\n sensitiveKeys: ReadonlySet<string>\n): Record<string, string> {\n const passthrough: Record<string, string> = {};\n for (const [k, v] of Object.entries(env)) {\n if (sensitiveKeys.has(k)) {\n args.push('-e', k);\n passthrough[k] = v;\n } else {\n args.push('-e', `${k}=${v}`);\n }\n }\n return passthrough;\n}\n\n/**\n * Build the `env` option for an `execFile` / `spawn` call that runs a\n * `docker run` whose args include passed-through sensitive keys (from\n * {@link appendEnvFlags}). Returns `{ env: {...process.env, ...passthrough} }`\n * so docker inherits the normal environment PLUS the sensitive values it\n * must resolve, or `{}` when there is nothing to pass through (preserving\n * the default inherited-environment behavior).\n */\nexport function execEnvForSecrets(passthrough: Record<string, string>): {\n env?: NodeJS.ProcessEnv;\n} {\n if (Object.keys(passthrough).length === 0) return {};\n return { env: { ...process.env, ...passthrough } };\n}\n\n/**\n * Returns a copy of `args` with any `-e <KEY>=<value>` pair whose KEY is\n * in {@link SENSITIVE_ENV_KEYS} replaced with `-e <KEY>=***`. The actual\n * `args` passed to `spawn` are never mutated — this is for log output\n * only. With {@link appendEnvFlags} routing credentials through the\n * value-less `-e KEY` form this rarely fires, but it stays as defense for\n * any inline-form credential that slips into a logged command.\n */\nexport function redactAwsCredentialsInArgs(args: readonly string[]): string[] {\n const out: string[] = [];\n for (let i = 0; i < args.length; i++) {\n const cur = args[i]!;\n const next = args[i + 1];\n if (cur === '-e' && typeof next === 'string') {\n const eqIdx = next.indexOf('=');\n if (eqIdx > 0) {\n const key = next.substring(0, eqIdx);\n if (SENSITIVE_ENV_KEYS.has(key)) {\n out.push('-e', `${key}=***`);\n i++;\n continue;\n }\n }\n }\n out.push(cur);\n }\n return out;\n}\n","import type { DockerCacheOption, DockerImageAssetSource } from '../types/assets.js';\nimport { getDockerCmd, runDockerStreaming, spawnStreaming } from '../utils/docker-cmd.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Shared `docker build` invocation used by both\n * `src/assets/docker-asset-publisher.ts` (publish to ECR) and\n * `src/local/docker-image-builder.ts` (run a container Lambda locally via\n * `cdkl invoke`).\n *\n * Parity with CDK CLI's `@aws-cdk/cdk-assets-lib`:\n * - Streaming spawn via `runDockerStreaming` (no `execFile` `maxBuffer`\n * ceiling — BuildKit progress on a `# syntax=docker/dockerfile:1`\n * frontend pull + multi-stage build can run into the tens of MB and\n * used to silently die at the 50 MB cap cdk-local previously set).\n * - Sets `BUILDX_NO_DEFAULT_ATTESTATIONS=1` so the resulting image stays\n * a single-arch image suitable for ECR pull (Docker Buildx otherwise\n * attaches provenance attestation manifests that confuse the publish\n * path).\n * - Resolves the docker binary via `getDockerCmd()` so users can swap to\n * `podman` / `finch` / `nerdctl` via the `CDK_DOCKER` env var.\n * - Full BuildKit flag set: `--build-context`, `--secret`, `--ssh`,\n * `--network`, `--cache-from`, `--cache-to`, `--no-cache`,\n * `--platform`, `--output`. Each is emitted only when the\n * corresponding asset-source field is set, so legacy builds without\n * these features still work unchanged.\n *\n * Build-arg iteration order is preserved per `Object.entries(...)` — this\n * is load-bearing for layer-cache reproducibility across both callers.\n *\n * The caller-supplied `wrapError` lets each consumer wrap the failure\n * with its own typed error class (`LocalInvokeBuildError` for local\n * invoke; a host's asset-publish path supplies its own).\n */\n\nexport interface BuildDockerImageOptions {\n /**\n * Local image tag (`--tag`) for `directory` mode. The caller chooses a\n * deterministic tag so subsequent runs hit Docker's layer cache\n * (local-invoke uses `cdkl-invoke-<hash>`). Ignored in `executable`\n * mode — there\n * the executable returns its own tag on stdout.\n */\n tag?: string;\n /**\n * Optional `--platform` override. When set, takes precedence over\n * `asset.source.platform` from the manifest. Used by `cdkl invoke`\n * to thread Lambda's `Architectures: [x86_64|arm64]` through to docker\n * build / run.\n */\n platform?: string;\n /**\n * Wrap the underlying docker / build-script failure in a typed error\n * specific to the call site.\n */\n wrapError: (stderr: string) => Error;\n}\n\n/**\n * Build a Docker image from a CDK asset source. Returns the local image\n * tag the caller should use for `docker tag` / `docker push` (publisher)\n * or `docker run` (local-invoke).\n *\n * Two source modes (mirrors CDK CLI):\n * - `executable`: run the user-supplied command, capture stdout, return\n * it as the local tag. The script is responsible for building AND\n * tagging; cdk-local just reads the tag from stdout. Used for Bazel /\n * custom build pipelines that produce images outside `docker build`.\n * - `directory`: standard `docker build <dir>` with the full BuildKit\n * flag set described above. Caller must pass `options.tag`.\n *\n * `executable` takes precedence when both fields are set (matches CDK CLI).\n */\nexport async function buildDockerImage(\n asset: { source: DockerImageAssetSource },\n cdkOutDir: string,\n options: BuildDockerImageOptions\n): Promise<string> {\n const source = asset.source;\n const logger = getLogger().child('docker-build');\n\n // Executable source: run the script and read stdout for the tag.\n //\n // We do NOT inject `BUILDX_NO_DEFAULT_ATTESTATIONS=1` into the\n // executable's env. The script may not be docker (Bazel, custom shell,\n // etc.) and even when it IS docker, the attestation suppression is the\n // SCRIPT's responsibility — CDK CLI's `cdk-assets-lib` `buildExternalAsset`\n // takes the same stance for parity. If the script invokes `docker build`\n // internally and the resulting image carries an attestation manifest\n // that breaks ECR pull, the user's script should set the env itself.\n if (source.executable && source.executable.length > 0) {\n const [cmd, ...args] = source.executable;\n if (!cmd) {\n throw options.wrapError('asset source.executable[] is empty');\n }\n // The executable runs from the asset directory when one is provided\n // (mirrors CDK CLI's `cwd: assetPath` in `buildExternalAsset`). When\n // `directory` is unset, the executable runs from `cdkOutDir`.\n const cwd = source.directory ? `${cdkOutDir}/${source.directory}` : cdkOutDir;\n\n logger.debug(\n `Building Docker image via executable: ${source.executable.join(' ')} (cwd=${cwd})`\n );\n\n let result;\n try {\n result = await spawnStreaming(cmd, args, { cwd });\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw options.wrapError(e.stderr || e.message || String(err));\n }\n const tag = result.stdout.trim();\n if (!tag) {\n throw options.wrapError(\n `docker build executable produced no output (expected the local image tag on stdout): ${cmd} ${args.join(' ')}`\n );\n }\n return tag;\n }\n\n // Directory source: standard docker build.\n if (!source.directory) {\n throw options.wrapError(\n `DockerImageAssetSource must set either 'directory' or 'executable' (got: ${JSON.stringify(source)})`\n );\n }\n if (!options.tag) {\n throw options.wrapError('buildDockerImage(directory mode) requires options.tag');\n }\n\n const buildArgs = buildDockerBuildCommand(source, options.tag, options.platform);\n // Use `.` as the context and set `cwd` to the asset directory. Mirrors\n // CDK CLI's `cdk-assets-lib` Docker.build — load-bearing because\n // BuildKit flags like `--secret id=X,src=relative.txt` /\n // `--build-context name=relative/path` resolve relative paths against\n // the build's cwd, NOT against the trailing context positional. Passing\n // an absolute context dir with no cwd silently breaks those flags.\n const contextDir = `${cdkOutDir}/${source.directory}`;\n buildArgs.push('.');\n\n logger.debug(`${getDockerCmd()} ${buildArgs.join(' ')} (cwd=${contextDir})`);\n\n try {\n await runDockerStreaming(buildArgs, {\n cwd: contextDir,\n // BUILDX_NO_DEFAULT_ATTESTATIONS=1 matches `cdk-assets-lib` — without\n // this, BuildKit/Buildx attaches provenance attestation manifests\n // that ECR's single-arch pull path rejects.\n env: { BUILDX_NO_DEFAULT_ATTESTATIONS: '1' },\n });\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw options.wrapError(e.stderr || e.message || String(err));\n }\n\n return options.tag;\n}\n\n/**\n * Construct the `docker build` argv (without the trailing context directory).\n *\n * Exported for unit-test inspection — keeps the flag-ordering assertions\n * independent of the spawn machinery.\n */\nexport function buildDockerBuildCommand(\n source: DockerImageAssetSource,\n tag: string,\n platformOverride?: string\n): string[] {\n // `--tag` (not `-t`) and `--file` (not `-f`) are the long-form names CDK\n // CLI's `cdk-assets-lib` emits. docker treats short / long aliases\n // identically, so this is cosmetic — but matching CDK CLI verbatim makes\n // a side-by-side comparison of the rendered argv (in --verbose logs)\n // grep-clean and removes one source of \"why is this slightly different?\"\n // confusion.\n const args: string[] = ['build', '--tag', tag];\n\n // Build args (Object.entries order preserved for layer-cache stability).\n if (source.dockerBuildArgs) {\n for (const [k, v] of Object.entries(source.dockerBuildArgs)) {\n args.push('--build-arg', `${k}=${v}`);\n }\n }\n\n // Build contexts (BuildKit 1.4+).\n if (source.dockerBuildContexts) {\n for (const [k, v] of Object.entries(source.dockerBuildContexts)) {\n args.push('--build-context', `${k}=${v}`);\n }\n }\n\n // Build secrets (BuildKit).\n if (source.dockerBuildSecrets) {\n for (const [k, v] of Object.entries(source.dockerBuildSecrets)) {\n args.push('--secret', `id=${k},${v}`);\n }\n }\n\n // SSH agent (BuildKit).\n if (source.dockerBuildSsh) {\n args.push('--ssh', source.dockerBuildSsh);\n }\n\n if (source.dockerBuildTarget) {\n args.push('--target', source.dockerBuildTarget);\n }\n\n if (source.dockerFile) {\n args.push('--file', source.dockerFile);\n }\n\n if (source.networkMode) {\n args.push('--network', source.networkMode);\n }\n\n // Platform: caller-provided override wins; otherwise source.platform from manifest.\n const platform = platformOverride ?? source.platform;\n if (platform) {\n args.push('--platform', platform);\n }\n\n // Outputs: CDK uses `--output=<value>` (single arg) which is what BuildKit\n // expects; the older `--output <value>` two-arg form works too but we\n // match CDK exactly for parity.\n if (source.dockerOutputs) {\n for (const output of source.dockerOutputs) {\n args.push(`--output=${output}`);\n }\n }\n\n if (source.cacheFrom) {\n for (const c of source.cacheFrom) {\n args.push('--cache-from', cacheOptionToFlag(c));\n }\n }\n if (source.cacheTo) {\n args.push('--cache-to', cacheOptionToFlag(source.cacheTo));\n }\n if (source.cacheDisabled) {\n args.push('--no-cache');\n }\n\n return args;\n}\n\nfunction cacheOptionToFlag(option: DockerCacheOption): string {\n let flag = `type=${option.type}`;\n if (option.params) {\n for (const [k, v] of Object.entries(option.params)) {\n flag += `,${k}=${v}`;\n }\n }\n return flag;\n}\n","import { ECRClient, GetAuthorizationTokenCommand } from '@aws-sdk/client-ecr';\nimport { AssumeRoleCommand, GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';\nimport {\n formatDockerLoginError,\n runDockerForeground,\n runDockerStreaming,\n} from '../utils/docker-cmd.js';\nimport { LocalInvokeBuildError } from '../utils/error-handler.js';\nimport { getLogger } from '../utils/logger.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * ECR pull fallback for `cdkl invoke` / `cdkl start-api` /\n * `cdkl run-task`. When the image URI resolves to an ECR repo but\n * doesn't match any cdk.out asset (typical when invoking a stack\n * deployed elsewhere or sharing a centralized registry), cdk-local\n * authenticates against the target registry and runs `docker pull`.\n *\n * **Cross-account / cross-region** (#455):\n * - Same-account, same-region: fast path. No STS hop. The default\n * credential chain is used directly for `ecr:GetAuthorizationToken`.\n * - `ecrRoleArn` is provided: `sts:AssumeRole` is issued via the\n * default credential chain to obtain temporary credentials for the\n * target account. The resulting credentials authenticate the ECR\n * client (regardless of region — the ECR client is built for the\n * URI's region, which can differ from the caller's profile region).\n * - Cross-account, NO `ecrRoleArn`: cdk-local falls through to the\n * default credential chain. This works when the caller has been\n * granted cross-account `ecr:GetAuthorizationToken` +\n * `ecr:BatchGetImage` permissions on the target repository via an\n * IAM policy; otherwise AWS rejects the call with `AccessDenied`\n * and the user is pointed at `--ecr-role-arn`.\n *\n * The `--no-pull` semantics (C3 in the design doc):\n * - When NOT set: `ecrLogin` + `docker pull <uri>`.\n * - When set: skip `docker pull`. If the image isn't in the local\n * cache, the subsequent `docker run` will fail; we surface a clearer\n * \"image not in local cache\" error here so the user knows to drop\n * `--no-pull` or pre-pull manually.\n */\n\n/** Regex matching the `<acct>.dkr.ecr.<region>.amazonaws.com/<repo>:<tag>` shape. */\nconst ECR_URI_REGEX = /^(\\d{12})\\.dkr\\.ecr\\.([^.]+)\\.amazonaws\\.com(?:\\.cn)?\\/([^:]+):(.+)$/;\n\nexport interface ParsedEcrUri {\n accountId: string;\n region: string;\n repository: string;\n tag: string;\n}\n\n/**\n * Parse an ECR image URI. Returns `undefined` for non-ECR URIs (typically:\n * Docker Hub, public.ecr.aws, gcr.io, ...) — those are user-managed\n * images we don't try to authenticate against.\n */\nexport function parseEcrUri(imageUri: string): ParsedEcrUri | undefined {\n const m = ECR_URI_REGEX.exec(imageUri);\n if (!m) return undefined;\n return {\n accountId: m[1]!,\n region: m[2]!,\n repository: m[3]!,\n tag: m[4]!,\n };\n}\n\nexport interface EcrPullOptions {\n /** When true, skip `docker pull` and require the image be in the local cache. */\n skipPull: boolean;\n /**\n * Caller's region (typically the CLI's resolved `--region`). Used only\n * to seed the STS client when `ecrRoleArn` is set — the ECR client is\n * always built for the URI's region (since cross-region pull is now\n * supported). When unset, env-var fallback applies via the SDK default\n * chain.\n */\n region?: string;\n /**\n * Optional role ARN to assume before authenticating against ECR\n * (#455). When set, `sts:AssumeRole` is issued via the default\n * credential chain and the resulting temporary credentials are used\n * for the ECR client. Required for cross-account pull when the\n * caller's identity does not already have `ecr:GetAuthorizationToken`\n * / `ecr:BatchGetImage` on the target repository.\n */\n ecrRoleArn?: string;\n /**\n * Optional AWS profile (the CLI's `--profile`). Threaded into the STS\n * caller-identity lookup and the ECR auth client so the pull\n * authenticates as the profile's account — for `--from-cfn-stack` the\n * image lives in the deployed (profile) account, and without this the\n * default credential chain authenticates as the wrong account and the\n * pull is denied / the auth token is rejected. Ignored on the\n * `ecrRoleArn` path, where the assumed-role credentials take over.\n */\n profile?: string;\n}\n\n/** STS-issued temporary credentials shape used to authenticate the ECR client. */\ninterface TempCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken: string;\n /**\n * Expiration timestamp recorded by STS. Used by the module-level cache below\n * to evict stale credentials before AWS itself rejects them. Optional because\n * the AWS SDK declares `Credentials.Expiration` as optional, but in practice\n * `AssumeRole` always returns it.\n */\n expiration?: Date;\n}\n\n/**\n * Module-level cache for STS-issued AssumeRole credentials, keyed by\n * `(ecrRoleArn, callerRegion)`. Closes the reviewer's MAJOR finding: ECS\n * run-task with N containers under one `--ecr-role-arn` would otherwise issue\n * N× `AssumeRole` and N× `GetCallerIdentity` for identical credentials valid\n * for 3600s. The cache keeps a 5-minute safety margin against the recorded\n * `Expiration` so STS-side / local-clock skew never lets a stale entry through.\n *\n * Cache key is intentionally `(roleArn, region)` rather than full caller\n * identity — STS issues per-region session creds, and a switch of `--region`\n * between two `local invoke` calls in the same process must re-issue.\n *\n * NOT cleared on process exit — Node's module scope evaporates with the\n * process, and no inter-process sharing is desired (each `cdkl invoke`\n * is its own isolated runtime).\n */\nconst ASSUMED_ROLE_CACHE = new Map<string, TempCredentials>();\n\n/**\n * Module-level cache for `STS:GetCallerIdentity`. The result is identity-only\n * (`Account`) and invariant for the lifetime of the process under one set of\n * default credentials. Keyed by `callerRegion` to avoid a cross-region leak\n * when the caller flips `AWS_REGION` mid-process (STS is global but the SDK\n * uses regional endpoints; the result is invariant in practice, but we key\n * on region for safety).\n */\nconst CALLER_IDENTITY_CACHE = new Map<string, string>();\n\n/** 5-minute safety margin against the recorded STS expiration timestamp. */\nconst STS_CREDENTIAL_SAFETY_MARGIN_MS = 5 * 60 * 1000;\n\n/**\n * Reset the STS credential caches. Exported for unit tests only — production\n * callers should never need this (the caches live for the process lifetime\n * and the per-`(roleArn, region)` keying already isolates concurrent runs).\n *\n * @internal\n */\nexport function __resetStsCachesForTesting(): void {\n ASSUMED_ROLE_CACHE.clear();\n CALLER_IDENTITY_CACHE.clear();\n}\n\nfunction isCredentialFresh(creds: TempCredentials): boolean {\n if (!creds.expiration) {\n // STS didn't return an Expiration — surface as stale rather than cache\n // forever. In practice AssumeRole always returns one.\n return false;\n }\n return creds.expiration.getTime() - Date.now() > STS_CREDENTIAL_SAFETY_MARGIN_MS;\n}\n\n/**\n * Pull (or verify locally cached) a container image from ECR.\n *\n * Auto-detects cross-account from `STS:GetCallerIdentity` and assumes\n * the supplied role when set. Returns the image URI the caller should\n * pass to `docker run` (same as the input — no rewriting).\n */\nexport async function pullEcrImage(imageUri: string, options: EcrPullOptions): Promise<string> {\n const logger = getLogger().child('ecr-puller');\n\n const parsed = parseEcrUri(imageUri);\n if (!parsed) {\n throw new LocalInvokeBuildError(\n `Image URI '${imageUri}' is not an ECR URI. ` +\n `${getEmbedConfig().cliName} invoke v1 only authenticates against ECR for the deployed-image fallback path.`\n );\n }\n\n const callerRegion =\n options.region ?? process.env['AWS_REGION'] ?? process.env['AWS_DEFAULT_REGION'];\n\n // `--no-pull` short-circuits before any AWS calls — verifying the local\n // cache needs no STS / ECR authentication. Hoisting this above the\n // `GetCallerIdentity` block avoids a wasted STS round-trip on every\n // container in an ECS run-task that pre-pulled the image manually.\n if (options.skipPull) {\n logger.info(`Skipping ECR pull (--no-pull). Verifying ${imageUri} is in local cache...`);\n await verifyImageInLocalCache(imageUri);\n return imageUri;\n }\n\n // Look up the caller's identity (cached per region — invariant for the\n // process's default credentials). Used both to log cross-account info AND\n // as the STS-AssumeRole source region. Failures here are fatal — without\n // an identity we cannot even tell whether this is a cross-account pull,\n // let alone authenticate.\n const callerIdentityKey = `${options.profile ?? '_noprofile'}|${callerRegion ?? '_unset'}`;\n let callerAccount = CALLER_IDENTITY_CACHE.get(callerIdentityKey);\n if (callerAccount === undefined) {\n const sts = new STSClient({\n ...(callerRegion && { region: callerRegion }),\n ...(options.profile && { profile: options.profile }),\n });\n try {\n const identity = await sts.send(new GetCallerIdentityCommand({}));\n if (!identity.Account) {\n throw new LocalInvokeBuildError(\n 'STS GetCallerIdentity returned no Account. Verify your AWS credentials.'\n );\n }\n callerAccount = identity.Account;\n CALLER_IDENTITY_CACHE.set(callerIdentityKey, callerAccount);\n } finally {\n sts.destroy();\n }\n }\n\n const crossAccount = callerAccount !== parsed.accountId;\n const crossRegion = callerRegion !== undefined && callerRegion !== parsed.region;\n\n // Optionally assume a role to gain credentials for the target account.\n // When `ecrRoleArn` is not set but the pull is cross-account, we\n // proceed with the caller's credentials anyway — IAM resource policies\n // on the ECR repository can grant cross-account access without\n // requiring AssumeRole. AWS surfaces a clear `AccessDenied` if the\n // grant is missing, and the caller can re-run with `--ecr-role-arn`.\n //\n // AssumeRole result cached per `(roleArn, region)` so an ECS run-task\n // with N containers under one `--ecr-role-arn` issues only 1× AssumeRole\n // for all N (sessions are valid 3600s, far longer than any practical\n // image-pull loop).\n let assumed: TempCredentials | undefined;\n if (options.ecrRoleArn) {\n const cacheKey = `${options.profile ?? '_noprofile'}|${options.ecrRoleArn}|${callerRegion ?? '_unset'}`;\n const cached = ASSUMED_ROLE_CACHE.get(cacheKey);\n if (cached && isCredentialFresh(cached)) {\n assumed = cached;\n logger.debug(`Reusing cached AssumeRole credentials for ${options.ecrRoleArn}`);\n } else {\n assumed = await assumeRoleForEcr(options.ecrRoleArn, callerRegion, options.profile, logger);\n ASSUMED_ROLE_CACHE.set(cacheKey, assumed);\n logger.info(\n `Assumed role ${options.ecrRoleArn} for ECR pull (account=${parsed.accountId}, region=${parsed.region})`\n );\n }\n } else if (crossAccount) {\n logger.info(\n `Cross-account ECR pull: image account ${parsed.accountId} != caller ${callerAccount}. ` +\n \"Using the caller's credentials; pass --ecr-role-arn <arn> if AWS rejects with AccessDenied.\"\n );\n }\n\n if (crossRegion) {\n logger.info(\n `Cross-region ECR pull: image region ${parsed.region} != caller ${callerRegion ?? '(unset)'}. ` +\n 'Authenticating against the image region directly.'\n );\n }\n\n // Authenticate against the URI's region (NOT the caller region).\n // When `assumed` is set, the ECR client uses those temporary\n // credentials; otherwise the default credential chain.\n const ecr = new ECRClient({\n region: parsed.region,\n // Assumed-role creds (the `ecrRoleArn` path) take precedence; otherwise\n // authenticate as the supplied `--profile` rather than the default chain.\n ...(assumed ? { credentials: assumed } : options.profile ? { profile: options.profile } : {}),\n });\n try {\n await ecrLogin(ecr, parsed.accountId, parsed.region);\n } finally {\n ecr.destroy();\n }\n\n logger.info(`Pulling ${imageUri}...`);\n try {\n await runDockerForeground(['pull', imageUri]);\n } catch (err) {\n const e = err as Error;\n throw new LocalInvokeBuildError(`docker pull ${imageUri} failed: ${e.message}`);\n }\n\n return imageUri;\n}\n\n/**\n * Assume the supplied role via the SDK default credential chain and\n * return the resulting temporary credentials. The STS client is built\n * with the caller's profile region (or unset) — STS is a global\n * service so the region is informational, but threading it through\n * mirrors the convention used by `src/utils/role-arn.ts`.\n */\nasync function assumeRoleForEcr(\n roleArn: string,\n callerRegion: string | undefined,\n profile: string | undefined,\n logger: ReturnType<ReturnType<typeof getLogger>['child']>\n): Promise<TempCredentials> {\n logger.debug(`Assuming role ${roleArn} for ECR pull...`);\n // Thread `--profile` so the AssumeRole source identity is the profile's,\n // matching the rest of the pull path.\n const sts = new STSClient({\n ...(callerRegion && { region: callerRegion }),\n ...(profile && { profile }),\n });\n try {\n const response = await sts.send(\n new AssumeRoleCommand({\n RoleArn: roleArn,\n RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-ecr-${Date.now()}`,\n DurationSeconds: 3600,\n })\n );\n const creds = response.Credentials;\n if (!creds || !creds.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) {\n throw new LocalInvokeBuildError(\n `AssumeRole(${roleArn}) returned no usable credentials. Verify the role's trust policy allows your identity to assume it.`\n );\n }\n return {\n accessKeyId: creds.AccessKeyId,\n secretAccessKey: creds.SecretAccessKey,\n sessionToken: creds.SessionToken,\n ...(creds.Expiration && { expiration: creds.Expiration }),\n };\n } catch (err) {\n if (err instanceof LocalInvokeBuildError) throw err;\n const reason = err instanceof Error ? err.message : String(err);\n throw new LocalInvokeBuildError(\n `Failed to assume role ${roleArn} for ECR pull: ${reason}. ` +\n \"Verify the role exists and its trust policy permits the caller's identity to assume it.\"\n );\n } finally {\n sts.destroy();\n }\n}\n\n/**\n * Authenticate the local docker daemon against the target ECR registry.\n * Self-contained in this module so the local-invoke path stays\n * independent of any full asset-publish path's larger surface area.\n */\nasync function ecrLogin(client: ECRClient, accountId: string, region: string): Promise<void> {\n const logger = getLogger().child('ecr-puller');\n logger.debug(`ECR login (account=${accountId}, region=${region})`);\n\n const response = await client.send(new GetAuthorizationTokenCommand({}));\n const authData = response.authorizationData?.[0];\n if (!authData?.authorizationToken) {\n throw new LocalInvokeBuildError('Failed to get ECR authorization token');\n }\n\n const token = Buffer.from(authData.authorizationToken, 'base64').toString();\n const [username, password] = token.split(':');\n if (!username || password === undefined) {\n throw new LocalInvokeBuildError(\n 'ECR authorization token has unexpected shape (missing username/password)'\n );\n }\n const endpoint = authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.amazonaws.com`;\n\n try {\n await runDockerStreaming(['login', '--username', username, '--password-stdin', endpoint], {\n input: password,\n });\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw new LocalInvokeBuildError(\n `ECR login failed: ${formatDockerLoginError(e.stderr || e.message || String(err), endpoint)}`\n );\n }\n}\n\n/**\n * `docker image inspect <uri>` returns non-zero when the image is not in\n * the local cache. Surface a clearer error than docker's raw output so\n * the user knows the `--no-pull` path requires a pre-cached image.\n */\nasync function verifyImageInLocalCache(imageUri: string): Promise<void> {\n try {\n await runDockerStreaming(['image', 'inspect', imageUri]);\n } catch {\n throw new LocalInvokeBuildError(\n `Image '${imageUri}' is not in the local docker cache and --no-pull was set. ` +\n `Either remove --no-pull (${getEmbedConfig().productName} will pull from ECR) or pre-pull the image manually with \\`docker pull\\`.`\n );\n }\n}\n\n/**\n * Check whether a docker image is in the local registry. Pure boolean —\n * the caller decides what message to surface on miss. Reused by the\n * `docker-image-builder` `--no-build` path so both the ECR-pull verifier\n * (above) and the local-build verifier route through one `docker image\n * inspect` shape.\n */\nexport async function isImageInLocalCache(imageRef: string): Promise<boolean> {\n try {\n await runDockerStreaming(['image', 'inspect', imageRef]);\n return true;\n } catch {\n return false;\n }\n}\n","import { createHash } from 'node:crypto';\nimport { buildDockerImage } from '../assets/docker-build.js';\nimport type { DockerImageAssetSource } from '../types/assets.js';\nimport { runDockerStreaming } from '../utils/docker-cmd.js';\nimport { LocalInvokeBuildError } from '../utils/error-handler.js';\nimport { getLogger } from '../utils/logger.js';\nimport { isImageInLocalCache } from './ecr-puller.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Local-build path for `cdkl invoke` against container Lambdas\n * (PR 5). Wraps `buildDockerImage` (in `src/assets/docker-build.ts`) with\n * a stable local tag derived from the asset source directory + Dockerfile\n * + build-args fingerprint, so successive `cdkl invoke` runs hit\n * Docker's layer cache instead of re-building from scratch.\n *\n * Failures are wrapped in `LocalInvokeBuildError` so the global error\n * handler surfaces a class specific to local-invoke instead of a more\n * general asset-build error.\n */\n\nexport interface BuildContainerImageOptions {\n /** Architecture from `Architectures: [x86_64|arm64]` (D5.6). Drives `--platform`. */\n architecture: 'x86_64' | 'arm64';\n /**\n * When true, skip `docker build` and require the previously-built image\n * tag to already be in the local docker registry. Surfaces a clear\n * \"image not in local registry and --no-build is set\" error when the\n * tag is missing so the user knows to drop `--no-build` or run\n * `docker build` manually first. Off by default; opt-in via the CLI's\n * `cdkl invoke --no-build` flag (closes #233).\n *\n * The local tag is deterministic — derived from the asset source\n * directory + Dockerfile + build-target + build-args fingerprint via\n * `computeLocalTag` — so a previous successful build under the same\n * inputs produces a tag that's still valid on subsequent\n * `--no-build` runs.\n */\n noBuild?: boolean;\n}\n\n/**\n * Build a Lambda container image from a CDK asset entry. Returns the\n * local image tag the caller should pass to `docker run`.\n *\n * When `options.noBuild` is set, skips `docker build` entirely and\n * verifies the deterministic local tag is already in the docker\n * registry; throws `LocalInvokeBuildError` with an actionable message\n * when the tag is missing.\n */\nexport async function buildContainerImage(\n asset: { source: DockerImageAssetSource },\n cdkOutDir: string,\n options: BuildContainerImageOptions\n): Promise<string> {\n const tag = computeLocalTag(asset.source);\n const platform = architectureToPlatform(options.architecture);\n const logger = getLogger().child('local-invoke-build');\n\n if (options.noBuild === true) {\n logger.info(`Skipping docker build (--no-build). Verifying ${tag} is in local registry...`);\n if (!(await isImageInLocalCache(tag))) {\n throw new LocalInvokeBuildError(\n `image '${tag}' not in local registry and --no-build is set; ` +\n 'remove --no-build or run `docker build` manually first.'\n );\n }\n logger.debug(`Local tag ${tag} is cached; skipping build.`);\n return tag;\n }\n\n logger.info(`Building container image (platform=${platform})...`);\n logger.debug(`Local tag: ${tag}`);\n\n // For `executable` source mode the user's script returns its own tag;\n // re-tag to our deterministic `cdkl-invoke-<hash>` so the\n // `--no-build` cache-lookup branch finds the image on subsequent runs.\n const actualTag = await buildDockerImage(asset, cdkOutDir, {\n tag,\n platform,\n wrapError: (stderr) =>\n new LocalInvokeBuildError(\n `docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(' ')}): ${stderr}`\n ),\n });\n if (actualTag !== tag) {\n logger.debug(`Re-tagging executable-built image '${actualTag}' → '${tag}'`);\n try {\n await runDockerStreaming(['tag', actualTag, tag]);\n } catch (err) {\n const e = err as { stderr?: string; message?: string };\n throw new LocalInvokeBuildError(\n `docker tag failed re-tagging '${actualTag}' → '${tag}': ${e.stderr?.trim() || e.message || String(err)}`\n );\n }\n }\n\n return tag;\n}\n\n/**\n * Translate Lambda's `Architectures` enum to a Docker `--platform` value.\n *\n * Critical bug fix C2 from the design doc — without this the build /\n * run step uses the host's default arch, which races on M1/M2 Macs\n * (arm64 host) with x86_64 Lambdas. Threaded into BOTH the build (here)\n * and the run path (`docker-runner.runDetached`).\n */\nexport function architectureToPlatform(architecture: 'x86_64' | 'arm64'): string {\n return architecture === 'arm64' ? 'linux/arm64' : 'linux/amd64';\n}\n\n/**\n * Build a stable local tag derived from the asset's build context.\n *\n * Fingerprints every field that affects the produced image so an iteration\n * that doesn't change those fields hits Docker's layer cache; an iteration\n * that DOES change them gets a fresh tag (the old tag stays around in\n * `docker images` but harmlessly). The fingerprint covers the full CDK\n * `DockerImageSource` schema so `dockerBuildSecrets` / `dockerBuildContexts`\n * / `cacheFrom` / etc. changes also bust the local cache as expected.\n */\nexport function computeLocalTag(source: DockerImageAssetSource): string {\n const hash = createHash('sha256');\n // Field-tagged fingerprint: prepend each field's name so adding new fields\n // later doesn't shift the digest for old shapes.\n pushField(hash, 'directory', source.directory ?? '');\n pushField(hash, 'executable', (source.executable ?? []).join(' '));\n pushField(hash, 'dockerFile', source.dockerFile ?? '');\n pushField(hash, 'dockerBuildTarget', source.dockerBuildTarget ?? '');\n pushField(hash, 'networkMode', source.networkMode ?? '');\n pushField(hash, 'platform', source.platform ?? '');\n pushField(hash, 'dockerBuildSsh', source.dockerBuildSsh ?? '');\n pushField(hash, 'cacheDisabled', source.cacheDisabled ? '1' : '0');\n pushMap(hash, 'dockerBuildArgs', source.dockerBuildArgs);\n pushMap(hash, 'dockerBuildContexts', source.dockerBuildContexts);\n pushMap(hash, 'dockerBuildSecrets', source.dockerBuildSecrets);\n pushField(hash, 'dockerOutputs', (source.dockerOutputs ?? []).join('\\x1f'));\n pushField(hash, 'cacheFrom', (source.cacheFrom ?? []).map((o) => JSON.stringify(o)).join('\\x1f'));\n pushField(hash, 'cacheTo', source.cacheTo ? JSON.stringify(source.cacheTo) : '');\n return `${getEmbedConfig().resourceNamePrefix}-invoke-${hash.digest('hex').slice(0, 16)}`;\n}\n\nfunction pushField(hash: ReturnType<typeof createHash>, name: string, value: string): void {\n hash.update(name);\n hash.update('=');\n hash.update(value);\n hash.update('\\0');\n}\n\nfunction pushMap(\n hash: ReturnType<typeof createHash>,\n name: string,\n value: Record<string, string> | undefined\n): void {\n hash.update(name);\n hash.update('={');\n if (value) {\n for (const [k, v] of Object.entries(value)) {\n hash.update(k);\n hash.update('=');\n hash.update(v);\n hash.update(';');\n }\n }\n hash.update('}\\0');\n}\n","import { Readable } from 'node:stream';\nimport { setTimeout as delay } from 'node:timers/promises';\n\n/**\n * HTTP client for the AWS Lambda Runtime Interface Emulator (RIE) baked\n * into the Lambda base images.\n *\n * RIE listens on `:8080` inside the container and exposes the same\n * Invoke endpoint the real Lambda runtime uses:\n *\n * POST /2015-03-31/functions/function/invocations\n *\n * The response body is the handler's return value (or the error\n * structure if the handler threw). HTTP status is 200 in both cases —\n * mirroring the real AWS API. The caller treats both as exit code 0\n * (per the issue's exit-code semantics).\n */\n\nconst INVOKE_PATH = '/2015-03-31/functions/function/invocations';\n\nexport interface InvokeResult {\n /** Parsed JSON response when the body is valid JSON, else the raw string. */\n payload: unknown;\n /** Raw response body (for logging / verbose output). */\n raw: string;\n}\n\n/**\n * Wait until RIE is ready to handle invokes on `host:port`. Returns once\n * a real HTTP probe succeeds; throws after `timeoutMs`.\n *\n * **Why HTTP and not TCP**: Docker's userland port forwarder accepts TCP\n * connections from the host as soon as `docker run -p` binds the port,\n * which is BEFORE the container's RIE process has actually started its\n * own HTTP listener. A TCP-only probe declares \"ready\" prematurely and\n * the very first `invokeRie` call lands during the gap with\n * `TypeError: fetch failed` (ECONNRESET on the unfinished HTTP socket).\n * The race is more pronounced on the Python base image than on the\n * Node.js one (the rapid layer's bootstrap path is longer for Python),\n * but it exists for both — see PR 4 of #224 for the failing-Node\n * reproducer that prompted the upgrade.\n *\n * The HTTP probe issues `POST /` with an empty body and treats every\n * server response (including 4xx — RIE answers 404 to unknown paths) as\n * \"ready\". Connect/reset/abort failures are treated as \"not ready yet\"\n * and retried; any other class of error (e.g. DNS failure) propagates\n * immediately — there's nothing to retry past.\n *\n * RIE is fast to start (<1s in practice) but the container's overall\n * boot can be slower on a cold daemon — 5s is the spec's recommended\n * window. We poll cheap (every 100ms) so the typical case is sub-second.\n *\n * After the HTTP probe succeeds, sleep a short post-ready settle window\n * before returning. Even when RIE answered an HTTP status, the very next\n * `fetch(/2015-03-31/...)` from the caller has been observed to race\n * against RIE on cold-loaded dockers and hit `TypeError: fetch failed`\n * (intermittent on slow / loaded daemons). 250ms is empirically\n * sufficient and is cheap for the common case; the `fetchWithStartupRetry`\n * helper inside `invokeRie` is the second line of defense for the case\n * where 250ms isn't enough.\n */\nexport async function waitForRieReady(host: string, port: number, timeoutMs = 5000): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n let lastError: unknown;\n\n while (Date.now() < deadline) {\n try {\n const ok = await httpProbe(host, port, 500);\n if (ok) {\n // Post-ready settle — see docstring above. Defense-in-depth on top\n // of the HTTP probe: even after a real HTTP response, the very\n // next `fetch()` against RIE has been observed to race on cold\n // dockers; a short pause shrinks the window further.\n await delay(250);\n return;\n }\n } catch (err) {\n lastError = err;\n }\n await delay(100);\n }\n\n const tail = lastError instanceof Error ? `: ${lastError.message}` : '';\n throw new Error(\n `RIE did not become ready on ${host}:${port} within ${timeoutMs}ms${tail}. ` +\n `The container may have exited early — check 'docker logs' output.`\n );\n}\n\n/**\n * Issue a tiny HTTP request to confirm RIE's HTTP listener is up (not\n * just the TCP forwarder Docker-side). Resolves `true` on any HTTP\n * response, `false` on connect / reset / abort. Other failure classes\n * (DNS, etc.) propagate so the caller can decide whether to retry.\n */\nasync function httpProbe(host: string, port: number, timeoutMs: number): Promise<boolean> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n // POST / instead of GET / so we exercise the same verb as the real\n // invoke; some HTTP stacks have separate readiness for read-only vs\n // write methods. Body is a tiny empty JSON object so we don't pay\n // a content-length parse on the way through.\n const response = await fetch(`http://${host}:${port}/`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: '{}',\n signal: controller.signal,\n });\n // Drain the body so the underlying socket is released back to the\n // pool. We don't care about the content — any response means RIE\n // is up.\n await response.text().catch(() => undefined);\n return true;\n } catch (err) {\n if (isTransientNetworkError(err)) return false;\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * `fetch()` failures during container boot manifest as a generic\n * `TypeError: fetch failed` whose `.cause` carries the underlying\n * Node `ECONNRESET` / `ECONNREFUSED` / `UND_ERR_SOCKET`. Treat all of\n * those as \"not ready, try again\" so the readiness loop covers the gap\n * between Docker's port forwarder accepting a TCP connection and the\n * container's RIE process being ready for HTTP.\n */\nfunction isTransientNetworkError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (err.name === 'AbortError') return true;\n if (err.name === 'TypeError' && err.message === 'fetch failed') return true;\n const cause = (err as { cause?: { code?: string } }).cause;\n if (cause?.code === 'ECONNRESET') return true;\n if (cause?.code === 'ECONNREFUSED') return true;\n if (cause?.code === 'UND_ERR_SOCKET') return true;\n return false;\n}\n\n/**\n * POST the event payload to RIE. The container CMD has already named the\n * handler, so the request URL is fixed.\n *\n * `timeoutMs` defaults to the function's `Timeout` * 2 (with a floor of\n * 30s) so a slow handler doesn't hang the CLI forever, but still has\n * room past the function's nominal timeout — RIE itself doesn't enforce\n * the timeout in v1, but it's the right ballpark.\n */\nexport async function invokeRie(\n host: string,\n port: number,\n event: unknown,\n timeoutMs: number\n): Promise<InvokeResult> {\n const url = `http://${host}:${port}${INVOKE_PATH}`;\n const body = JSON.stringify(event ?? {});\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let response: Response;\n try {\n response = await fetchWithStartupRetry(url, body, controller.signal);\n } catch (err) {\n if ((err as { name?: string }).name === 'AbortError') {\n throw new Error(\n `RIE invoke at ${url} timed out after ${timeoutMs}ms. The handler may be hung; check container logs.`\n );\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n\n const raw = await response.text();\n let payload: unknown = raw;\n try {\n payload = JSON.parse(raw);\n } catch {\n // Non-JSON body — surface it as-is. The Lambda runtime always\n // emits JSON for valid handler returns, but a misconfigured\n // container could return plain text and we should not crash.\n }\n return { payload, raw };\n}\n\n/**\n * Wrap a single POST against RIE in a tiny startup-retry loop. Even\n * after `waitForRieReady`'s HTTP probe has succeeded and the post-ready\n * settle has elapsed, the next `fetch()` has been observed to race\n * against RIE's HTTP handler on cold dockers. The race manifests as\n * Node's `TypeError: fetch failed` (a pre-response, connection-level\n * error with no HTTP status). We retry twice with a 200ms backoff —\n * cheap when the race doesn't trigger, decisive when it does.\n *\n * Once a real HTTP response (any status) is observed, we return it\n * unchanged: the handler may have legitimately failed, and that's not\n * something we should retry. Abort errors propagate immediately so the\n * outer timeout still wins.\n */\nasync function fetchWithStartupRetry(\n url: string,\n body: string,\n signal: AbortSignal,\n extraHeaders?: Record<string, string>\n): Promise<Response> {\n const maxAttempts = 3;\n let lastError: unknown;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...extraHeaders },\n body,\n signal,\n });\n } catch (err) {\n const name = (err as { name?: string }).name;\n if (name === 'AbortError') throw err;\n lastError = err;\n if (attempt === maxAttempts) break;\n await delay(200);\n }\n }\n throw lastError;\n}\n\n/**\n * Parsed prelude metadata emitted by a streaming Lambda response.\n *\n * The Lambda runtime's streaming response format (verified empirically\n * against `public.ecr.aws/lambda/nodejs:20` RIE on 2026-05-22 for #467):\n *\n * <JSON prelude bytes> <8 NULL bytes> <raw body bytes...>\n *\n * The prelude is a JSON object with `statusCode`, `headers`, and\n * (optionally) `cookies`, produced by the handler's call to\n * `awslambda.HttpResponseStream.from(stream, metadata)`. The body\n * is the bytes the handler subsequently `write`'d / piped into the\n * stream — no framing, no chunked-encoding markers, just raw bytes\n * that the HTTP layer can pipe straight through.\n */\nexport interface StreamingPrelude {\n /** Lambda-declared HTTP status. */\n statusCode: number;\n /** Lambda-declared response headers (case as emitted). */\n headers: Record<string, string>;\n /** Lambda-declared cookies (HTTP API v2 shape — each rendered as a separate Set-Cookie). */\n cookies?: string[];\n}\n\n/** Resolved streaming invocation result: parsed prelude + a Readable of the body chunks. */\nexport interface StreamingInvokeResult {\n prelude: StreamingPrelude;\n /**\n * The body stream — a Node `Readable` that emits the chunks the handler\n * streamed AFTER the prelude/separator. Pipe directly into a\n * `ServerResponse` with `Transfer-Encoding: chunked`.\n */\n body: Readable;\n}\n\n/**\n * The 8-NULL-byte separator AWS Lambda RIE writes between the JSON\n * metadata prelude and the streaming body chunks. Empirically verified\n * — see `StreamingPrelude` docstring above.\n */\nconst STREAM_PRELUDE_SEPARATOR = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]);\n\n/**\n * Maximum bytes we'll buffer searching for the prelude separator before\n * giving up. 1 MiB is far past anything Lambda's streamifyResponse would\n * emit as metadata (typical preludes are <500 bytes) — a runaway here\n * indicates the handler didn't call `HttpResponseStream.from` at all, in\n * which case we want to fail fast rather than buffer the whole body.\n */\nexport const STREAM_PRELUDE_MAX_BYTES = 1024 * 1024;\n\n/**\n * Maximum cumulative bytes of body the streaming Readable will push\n * before destroying itself with a clear error (defense-in-depth against\n * a buggy / malicious handler streaming gigabytes — Node's chunked-pipe\n * machinery handles per-chunk backpressure, but the running total can\n * still grow without bound on a slow consumer).\n *\n * 100 MiB is the default cap — generous enough that no realistic\n * dev-loop streaming response (token-by-token LLM output, large-file\n * download, video segment) hits it, low enough that a genuine runaway\n * surfaces locally before swap pressure kicks in. The HTTP server\n * converts the destroyed Readable to a truncated response (best-effort —\n * headers may already be on the wire).\n *\n * Consistent with {@link STREAM_PRELUDE_MAX_BYTES} (1 MiB cap on the\n * pre-body buffer); this is the post-body counterpart.\n */\nexport const STREAM_BODY_MAX_BYTES = 100 * 1024 * 1024;\n\n/**\n * POST the event payload to RIE with the `streaming` response-mode\n * header, parse the JSON prelude out of the response bytes, and return\n * a Readable carrying the post-separator body chunks.\n *\n * Why a separate function from `invokeRie`: the prelude/separator/body\n * framing is incompatible with the buffered-response `text()` consumer.\n * Buffered routes still use `invokeRie`; only Function URLs with\n * `InvokeMode: RESPONSE_STREAM` use this path.\n *\n * The `Lambda-Runtime-Function-Response-Mode: streaming` request header\n * tells RIE we want the streaming protocol. (RIE happens to emit the\n * same protocol for `streamifyResponse`-wrapped handlers regardless of\n * the header, but setting it makes the contract explicit and survives\n * future RIE behavior changes.)\n *\n * **`timeoutMs` bounds the TOTAL wall time of the entire streaming\n * exchange**, NOT just the prelude wait — the single armed `setTimeout`\n * covers both the prelude arrival AND the body drain. Once it fires,\n * `controller.abort()` destroys the underlying Readable, so a\n * legitimately long-lived streaming handler (e.g. a 15-minute AI / LLM\n * proxy) will have its connection torn down mid-stream even though\n * bytes are arriving correctly. Callers MUST size `timeoutMs` to cover\n * the longest expected handler stream, NOT just the time to first byte.\n *\n * Convention: pass `lambda.Timeout * 2` with a 30-second floor — same\n * order-of-magnitude formula as `invokeRie`, but the absolute value\n * differs because streaming handlers can intentionally run for the full\n * Lambda timeout (default 15 minutes for streaming-capable functions).\n * Splitting the bound into a strict prelude timer + a per-chunk idle\n * timer that resets on each chunk is deferred to a follow-up — see\n * issue #503 item 1 for the design discussion.\n *\n * The body Readable is additionally guarded by {@link STREAM_BODY_MAX_BYTES}\n * (100 MiB by default) so a runaway handler can't blow host memory; the\n * Readable is destroyed with a clear error when the cap trips.\n */\nexport async function invokeRieStreaming(\n host: string,\n port: number,\n event: unknown,\n timeoutMs: number\n): Promise<StreamingInvokeResult> {\n const url = `http://${host}:${port}${INVOKE_PATH}`;\n const body = JSON.stringify(event ?? {});\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let response: Response;\n try {\n response = await fetchWithStartupRetry(url, body, controller.signal, {\n 'Lambda-Runtime-Function-Response-Mode': 'streaming',\n });\n } catch (err) {\n clearTimeout(timer);\n if ((err as { name?: string }).name === 'AbortError') {\n throw new Error(\n `RIE streaming invoke at ${url} timed out after ${timeoutMs}ms. The handler may be hung; check container logs.`\n );\n }\n throw err;\n }\n\n if (!response.body) {\n clearTimeout(timer);\n throw new Error(`RIE streaming invoke at ${url} returned no response body.`);\n }\n\n // Split the response stream at the 8-NULL-byte separator: prelude\n // bytes accumulate in `preludeBuf`, then the remaining bytes (plus\n // the rest of the stream) become the returned body Readable.\n const reader = response.body.getReader();\n let preludeBytes = Buffer.alloc(0);\n let bodyTail: Buffer | undefined;\n let separatorIdx = -1;\n\n while (separatorIdx < 0) {\n const { value, done } = await reader.read();\n if (done) break;\n const chunk = Buffer.from(value);\n preludeBytes = Buffer.concat([preludeBytes, chunk]);\n separatorIdx = preludeBytes.indexOf(STREAM_PRELUDE_SEPARATOR);\n if (separatorIdx >= 0) {\n // Capture any body bytes already present in this chunk; everything\n // AFTER the separator belongs to the body.\n bodyTail = preludeBytes.subarray(separatorIdx + STREAM_PRELUDE_SEPARATOR.length);\n preludeBytes = preludeBytes.subarray(0, separatorIdx);\n break;\n }\n if (preludeBytes.length > STREAM_PRELUDE_MAX_BYTES) {\n clearTimeout(timer);\n reader.cancel().catch(() => undefined);\n throw new Error(\n `RIE streaming response did not emit the prelude/body separator within ${STREAM_PRELUDE_MAX_BYTES} bytes. ` +\n `The handler likely did not call awslambda.HttpResponseStream.from(stream, metadata).`\n );\n }\n }\n\n // Whether we synthesized a default prelude because no separator was found\n // in the response. See the fix block below for the rationale.\n let preludeSynthesized = false;\n\n if (separatorIdx < 0) {\n // No prelude/body separator found in the entire response. Two real-world\n // scenarios produce this — empirically verified 2026-05-27\n // against `public.ecr.aws/lambda/nodejs:22`:\n //\n // (1) The handler is wrapped by `awslambda.streamifyResponse(...)` AND\n // uses the documented `responseStream.setContentType('...')` +\n // `responseStream.write(...)` shortcut WITHOUT explicitly calling\n // `awslambda.HttpResponseStream.from(stream, metadata)`. Production\n // AWS Lambda + Function URL handles this — the runtime auto-completes\n // the prelude lazily — but RIE emits only the raw body bytes the\n // handler wrote, no prelude+separator framing. This is the most\n // common case in the wild (the `setContentType` shortcut appears in\n // most tutorials + AWS docs).\n //\n // (2) The handler crashed mid-invoke and RIE emitted a Lambda error\n // envelope (`{\"errorType\":\"...\",\"errorMessage\":\"...\"}`) instead of\n // the prelude+separator framing. RIE itself reports\n // `INVOKE RTDONE(status: success, produced bytes: 0)` because from\n // its perspective the handler never finalized the streaming\n // response; the envelope bytes are sidecar diagnostics.\n //\n // In BOTH cases the right move is to NOT reject the invocation. We\n // synthesize a default prelude (status 200, `application/octet-stream`)\n // and surface the buffered bytes as the HTTP body. Case (1) lets the\n // handler work locally exactly as it does in production. Case (2)\n // surfaces the Lambda error envelope verbatim so the dev can read it\n // from the curl response (much better UX than cdk-local swallowing the\n // bytes behind a generic \"RIE streaming invoke failed\").\n //\n // The genuinely-empty case (zero buffered bytes — handler threw before\n // any write AND RIE didn't emit a diagnostic envelope) still falls\n // through to the original error path: there's no body worth surfacing,\n // and the rapid log is the right place for the dev to read the cause.\n if (preludeBytes.length === 0) {\n clearTimeout(timer);\n throw new Error(\n `RIE streaming response ended with zero bytes. The handler likely threw before any write — check container logs.`\n );\n }\n preludeSynthesized = true;\n bodyTail = preludeBytes;\n preludeBytes = Buffer.alloc(0);\n }\n\n let prelude: StreamingPrelude;\n if (preludeSynthesized) {\n // Default prelude when the handler bypassed `HttpResponseStream.from(...)`.\n // `application/octet-stream` matches the documented AWS default; consumers\n // that care about Content-Type should call `HttpResponseStream.from(...)`\n // explicitly with the right metadata.\n prelude = {\n statusCode: 200,\n headers: { 'Content-Type': 'application/octet-stream' },\n };\n } else {\n try {\n prelude = parseStreamingPrelude(preludeBytes.toString('utf8'));\n } catch (err) {\n clearTimeout(timer);\n reader.cancel().catch(() => undefined);\n throw new Error(\n `RIE streaming response prelude is not valid JSON: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n\n // Build a Readable that emits any already-buffered body bytes first,\n // then drains the remaining response stream until done. The timeout\n // is kept armed so a hung mid-body handler still aborts.\n const stream = new Readable({\n read() {\n // No-op — chunks are pushed by the IIFE below.\n },\n });\n\n // Track cumulative body bytes pushed into the Readable so we can\n // destroy the stream if it crosses the safety cap. See\n // STREAM_BODY_MAX_BYTES for the rationale.\n let bodyBytesPushed = 0;\n const exceedsCap = (added: number): boolean => {\n bodyBytesPushed += added;\n return bodyBytesPushed > STREAM_BODY_MAX_BYTES;\n };\n\n void (async () => {\n try {\n if (bodyTail && bodyTail.length > 0) {\n if (exceedsCap(bodyTail.length)) {\n reader.cancel().catch(() => undefined);\n stream.destroy(\n new Error(\n `RIE streaming body exceeded ${STREAM_BODY_MAX_BYTES} bytes — destroying stream.`\n )\n );\n return;\n }\n stream.push(bodyTail);\n }\n // Drain the rest. The reader has internal backpressure; pushing\n // into the Readable returns false on overflow but Node's pipe()\n // handles draining via the 'drain' event.\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n const chunk = Buffer.from(value);\n if (exceedsCap(chunk.length)) {\n reader.cancel().catch(() => undefined);\n stream.destroy(\n new Error(\n `RIE streaming body exceeded ${STREAM_BODY_MAX_BYTES} bytes — destroying stream.`\n )\n );\n return;\n }\n stream.push(chunk);\n }\n stream.push(null);\n } catch (err) {\n stream.destroy(err instanceof Error ? err : new Error(String(err)));\n } finally {\n clearTimeout(timer);\n }\n })();\n\n return { prelude, body: stream };\n}\n\n/**\n * Parse a streaming prelude payload (JSON text). Normalizes the shape\n * the http-server consumes: `statusCode` is coerced to a number (RIE\n * sometimes emits it as a string), `headers` is always an object (the\n * handler may omit it), `cookies` is preserved only when an array.\n *\n * Exported for unit tests. Throws on invalid JSON or a non-numeric\n * statusCode (cdk-local cannot map that to HTTP).\n */\nexport function parseStreamingPrelude(text: string): StreamingPrelude {\n const trimmed = text.trim();\n if (trimmed.length === 0) {\n throw new Error('empty prelude');\n }\n const raw = JSON.parse(trimmed) as unknown;\n if (!raw || typeof raw !== 'object') {\n throw new Error('prelude is not a JSON object');\n }\n const obj = raw as Record<string, unknown>;\n\n const statusRaw = obj['statusCode'];\n let statusCode: number;\n if (typeof statusRaw === 'number' && Number.isFinite(statusRaw)) {\n statusCode = Math.trunc(statusRaw);\n } else if (typeof statusRaw === 'string' && /^[0-9]+$/.test(statusRaw)) {\n statusCode = Number.parseInt(statusRaw, 10);\n } else {\n throw new Error(`statusCode must be a number (got ${typeof statusRaw})`);\n }\n\n const headers: Record<string, string> = {};\n const headersRaw = obj['headers'];\n if (headersRaw && typeof headersRaw === 'object') {\n for (const [k, v] of Object.entries(headersRaw as Record<string, unknown>)) {\n if (v === null || v === undefined) continue;\n // AWS's documented shape is string/number/boolean scalars; map\n // everything else through JSON.stringify (defensive — a buggy\n // handler emitting an object would otherwise log `[object Object]`).\n if (typeof v === 'string') {\n headers[k] = v;\n } else if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') {\n headers[k] = String(v);\n } else {\n headers[k] = JSON.stringify(v) ?? '';\n }\n }\n }\n\n const result: StreamingPrelude = { statusCode, headers };\n const cookiesRaw = obj['cookies'];\n if (Array.isArray(cookiesRaw)) {\n result.cookies = cookiesRaw.map((c) => String(c));\n }\n return result;\n}\n","import { readFile } from 'fs/promises';\nimport { join } from 'path';\nimport type { AssetManifest, DockerImageAsset, FileAsset } from '../types/assets.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Asset manifest loader\n *\n * Loads and parses CDK asset manifests from the CDK output directory\n */\nexport class AssetManifestLoader {\n private logger = getLogger().child('AssetManifestLoader');\n\n /**\n * Load asset manifest from CDK output directory\n *\n * @param cdkOutputDir CDK output directory (e.g., \"cdk.out\")\n * @param stackName Stack name\n * @returns Asset manifest or null if not found\n */\n async loadManifest(cdkOutputDir: string, stackName: string): Promise<AssetManifest | null> {\n const manifestPath = join(cdkOutputDir, `${stackName}.assets.json`);\n\n try {\n this.logger.debug(`Loading asset manifest from: ${manifestPath}`);\n const content = await readFile(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as AssetManifest;\n\n this.logger.debug(\n `Loaded asset manifest: ${Object.keys(manifest.files).length} file assets, ` +\n `${Object.keys(manifest.dockerImages).length} docker image assets`\n );\n\n return manifest;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n this.logger.debug(`Asset manifest not found: ${manifestPath}`);\n return null;\n }\n\n throw new Error(\n `Failed to load asset manifest from ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Get file assets from manifest (excludes CloudFormation templates)\n *\n * @param manifest Asset manifest\n * @returns Map of asset hash to file asset\n */\n getFileAssets(manifest: AssetManifest): Map<string, FileAsset> {\n const fileAssets = new Map<string, FileAsset>();\n\n for (const [assetHash, asset] of Object.entries(manifest.files)) {\n // Skip CloudFormation templates (they have .json extension)\n if (asset.source.path.endsWith('.json') || asset.source.path.endsWith('.template.json')) {\n this.logger.debug(`Skipping CloudFormation template asset: ${asset.displayName}`);\n continue;\n }\n\n fileAssets.set(assetHash, asset);\n }\n\n this.logger.debug(`Found ${fileAssets.size} file assets (excluding templates)`);\n return fileAssets;\n }\n\n /**\n * Get asset source path (absolute path)\n *\n * @param cdkOutputDir CDK output directory\n * @param asset File asset\n * @returns Absolute path to asset source\n */\n getAssetSourcePath(cdkOutputDir: string, asset: FileAsset): string {\n return join(cdkOutputDir, asset.source.path);\n }\n\n /**\n * Resolve asset destination values (replace ${AWS::AccountId}, ${AWS::Region}, etc.)\n *\n * @param value Value with placeholders\n * @param accountId AWS account ID\n * @param region AWS region\n * @param partition AWS partition (default: \"aws\")\n * @returns Resolved value\n */\n resolveAssetDestinationValue(\n value: string,\n accountId: string,\n region: string,\n partition = 'aws'\n ): string {\n return value\n .replace(/\\$\\{AWS::AccountId\\}/g, accountId)\n .replace(/\\$\\{AWS::Region\\}/g, region)\n .replace(/\\$\\{AWS::Partition\\}/g, partition);\n }\n}\n\n/**\n * Look up the docker-image asset that backs a Lambda's `Code.ImageUri`.\n *\n * The CDK template synthesizes `Code.ImageUri` as a `Fn::Sub` whose body\n * references the bootstrap ECR repo and ends in `:<hash>` — that hash is\n * the same key used in `manifest.dockerImages[<hash>]`. cdk-local extracts the\n * hash by walking known image-URI shapes; on miss, when the manifest has\n * exactly one Docker image, we fall back to it (single-asset heuristic) so\n * locally-built non-bootstrapped images still work. This is documented as\n * a v1 limitation; immutable digest pins (`@sha256:<digest>`) hit the same\n * fallback path.\n *\n * Returns the `(hash, asset)` pair when matched, or `undefined` when both\n * the regex AND the single-asset fallback miss (typically: 0 docker assets,\n * or 2+ docker assets with no hash match — the caller should treat this as\n * \"fall through to the ECR-pull path\").\n *\n * Exported as a free function (not a method) so the local-invoke modules\n * can reuse it without depending on the `AssetManifestLoader` instance —\n * the manifest itself is a plain JSON shape.\n */\nexport function getDockerImageBySourceHash(\n manifest: AssetManifest,\n imageUri: string\n): { hash: string; asset: DockerImageAsset } | undefined {\n const dockerImages = manifest.dockerImages ?? {};\n const entries = Object.entries(dockerImages);\n if (entries.length === 0) return undefined;\n\n // Try to extract the hash from the ImageUri tail. Match `:<hash>` (NOT\n // `@sha256:<digest>` — those are immutable digest pins which never carry\n // the source hash; we fall through to the single-asset heuristic for\n // those). The hash itself is hex-only in CDK's bootstrap layout.\n const hash = extractHashFromImageUri(imageUri);\n if (hash !== undefined) {\n const asset = dockerImages[hash];\n if (asset) {\n return { hash, asset };\n }\n }\n\n // Single-asset fallback: when the user has exactly one Docker image in\n // the stack, it's almost certainly the one being invoked. Avoids\n // hard-failing on hash-extraction misses that would otherwise be common\n // (digest pins, custom Code.fromAssetImage forms, etc.).\n if (entries.length === 1) {\n const [singleHash, singleAsset] = entries[0]!;\n return { hash: singleHash, asset: singleAsset };\n }\n\n return undefined;\n}\n\n/**\n * Extract the source hash from a Lambda `Code.ImageUri` string. CDK's\n * bootstrap layout ends every image URI in `:<hex-hash>`, and that hash\n * is the same key used in the asset manifest's `dockerImages` map.\n *\n * Returns `undefined` for shapes we can't parse (digest pins, missing tag,\n * etc.) — the caller falls back to the single-asset heuristic.\n */\nfunction extractHashFromImageUri(imageUri: string): string | undefined {\n // Reject digest-pinned URIs. `<repo>@sha256:<digest>` carries no hash.\n if (imageUri.includes('@sha256:')) return undefined;\n\n // Match `:<hex-hash>` at the very end of the URI. `cdk-hnb659fds-container-assets-...:<64-hex>`\n // is the typical shape; we accept any 8+-character hex tail to be lenient.\n const match = /:([a-f0-9]{8,})$/.exec(imageUri);\n return match?.[1];\n}\n","/**\n * Memoize an async cleanup function so concurrent / repeated callers\n * await the SAME underlying invocation instead of issuing parallel runs.\n *\n * Motivating use case: long-running CLI commands (`cdkl invoke`,\n * `cdkl start-api`, `cdkl run-task`) wire the same cleanup\n * helper to BOTH a `process.on('SIGINT', ...)` handler AND an outer\n * `try`/`finally`. A ^C that lands during normal unwind would otherwise\n * race two cleanup runs against shared mutable state (container IDs,\n * tmpdir paths, log-stopper handles), risking double `docker rm -f` and\n * iterator-mid-mutation bugs.\n *\n * Contract:\n *\n * - The returned function is async and resolves after the wrapped\n * `fn` resolves (success OR throw).\n * - The wrapped `fn` is invoked exactly once across all calls to the\n * returned function. Every concurrent / later caller awaits that\n * same promise.\n * - Caller-internal per-step idempotency (e.g. `if (containerId)`\n * guards inside `fn`) is preserved — this helper only ensures the\n * iteration over those mutable cells doesn't race, it does NOT\n * replace the per-step guards.\n * - Throws inside `fn` are caught and logged via the optional\n * `onError` callback so cleanup never masks a real handler error.\n * The returned promise still resolves (the cleanup completed in the\n * observable sense — we want callers to await it and exit).\n */\nexport function singleFlight(\n fn: () => Promise<void>,\n onError?: (err: unknown) => void\n): () => Promise<void> {\n let promise: Promise<void> | undefined;\n return async (): Promise<void> => {\n if (!promise) {\n promise = (async () => {\n try {\n await fn();\n } catch (err) {\n if (onError) onError(err);\n }\n })();\n }\n await promise;\n };\n}\n","// Profile-aware credentials file mount for cdk-local Lambda containers.\n//\n// Background: cdk-local forwards `--profile <p>`-resolved credentials to\n// the Lambda container as `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` /\n// `AWS_SESSION_TOKEN` env vars. The SDK's default credential provider chain\n// reads those env vars, so the common handler pattern\n// (`new SecretsManagerClient({ region })`) works.\n//\n// What this module adds: handlers that explicitly call\n// `fromIni({ profile: '<name>' })` bypass the env-var chain and look for\n// `[<name>]` in `~/.aws/credentials` (or `AWS_SHARED_CREDENTIALS_FILE`).\n// Inside the Lambda container neither file exists by default, so those\n// handlers fail locally even when production AWS Lambda + IAM-role-baked-\n// profile setups (Lambda Layer with credentials etc.) make them work.\n//\n// Fix: when `--profile <name>` is passed, ALSO write a temp credentials\n// file with the resolved creds under `[<name>]`, bind-mount it into the\n// container, and set `AWS_SHARED_CREDENTIALS_FILE=<containerPath>` +\n// `AWS_PROFILE=<name>` env vars. Now both code paths work:\n//\n// - Default chain: reads `AWS_ACCESS_KEY_ID` etc. (existing behavior)\n// - `fromIni({ profile: '<name>' })`: reads the mounted file via\n// `AWS_SHARED_CREDENTIALS_FILE`, finds `[<name>]`, returns the same\n// resolved creds\n//\n// The profile NAME inside the container matches what the user passed via\n// `--profile` so handler code `fromIni({ profile: '<name>' })` matches\n// without source changes.\n\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport * as path from 'node:path';\nimport { getEmbedConfig } from '../../local/embed-config.js';\n\n/**\n * Path inside the container where the credentials file is mounted. Fixed\n * per run (not user-configurable) so the env-var injection is stable. The\n * default `/cdk-local-aws/` is outside `/var/task` (the Lambda code mount)\n * and outside `/root/` (which the user's handler may bind-mount or\n * modify), so there is no collision risk with the user's payload. The\n * `${awsBindMountPath}` segment honors the active embed config.\n */\nexport function getContainerAwsCredentialsPath(): string {\n return `${getEmbedConfig().awsBindMountPath}/credentials`;\n}\n\n/**\n * Resolved profile credentials file ready to mount into a Lambda container.\n *\n * `hostPath` is the absolute path on the host\n * (`/tmp/cdk-local-profile-creds-<rand>/credentials`).\n * `dispose` removes the host-side file + its parent tempdir; safe to call\n * multiple times (idempotent rm).\n */\nexport interface ProfileCredentialsFile {\n hostPath: string;\n containerPath: string;\n profileName: string;\n dispose: () => Promise<void>;\n}\n\n/**\n * Write a temporary AWS shared-credentials file containing the resolved\n * `--profile <name>` credentials, ready to bind-mount into a Lambda\n * container at {@link getContainerAwsCredentialsPath}.\n *\n * The file content is the standard `[profile-name]` INI shape:\n *\n * [<profileName>]\n * aws_access_key_id = <accessKeyId>\n * aws_secret_access_key = <secretAccessKey>\n * aws_session_token = <sessionToken> ← only when present\n *\n * `aws_session_token` is omitted when the resolved profile produced\n * long-lived (non-STS) credentials, mirroring the same logic\n * `applyProfileCredentialsOverlay` uses for env-var injection.\n *\n * Caller is responsible for invoking `dispose()` when the container pool\n * tears down (e.g., on `SIGINT` via `singleFlight` cleanup). Leaving the\n * file behind in `/tmp` is a security smell (temp credentials live on\n * disk).\n */\nexport async function writeProfileCredentialsFile(\n profileName: string,\n creds: { accessKeyId: string; secretAccessKey: string; sessionToken?: string }\n): Promise<ProfileCredentialsFile> {\n // Validate the profile name before interpolating into the INI section\n // header / AWS_PROFILE env var.\n // The injection surface is local-dev-only (the caller is the user's\n // own `--profile <name>` arg) so this is hardening, not security\n // boundary — but a value containing `]` would silently start a second\n // INI section, and a value containing newlines would break the\n // `-e AWS_PROFILE=...` docker-run env line. Reject at the helper\n // boundary so the caller never has to think about it.\n if (profileName === '') {\n throw new Error('writeProfileCredentialsFile: profile name must not be empty.');\n }\n if (/[\\r\\n[\\]]/.test(profileName)) {\n throw new Error(\n `writeProfileCredentialsFile: profile name '${profileName}' contains a forbidden character ` +\n `(any of CR, LF, '[', ']' would corrupt the INI file or the docker -e env var).`\n );\n }\n const dir = await mkdtemp(path.join(tmpdir(), `${getEmbedConfig().productName}-profile-creds-`));\n const hostPath = path.join(dir, 'credentials');\n const lines: string[] = [\n `[${profileName}]`,\n `aws_access_key_id = ${creds.accessKeyId}`,\n `aws_secret_access_key = ${creds.secretAccessKey}`,\n ];\n if (creds.sessionToken) {\n lines.push(`aws_session_token = ${creds.sessionToken}`);\n }\n // Trailing newline for POSIX-text-file convention; some INI parsers\n // (including AWS SDK's older versions) reject files without a final\n // newline.\n await writeFile(hostPath, lines.join('\\n') + '\\n', { mode: 0o600 });\n return {\n hostPath,\n containerPath: getContainerAwsCredentialsPath(),\n profileName,\n dispose: async () => {\n // `recursive: true` removes the credentials file + its parent\n // tempdir in one shot. `force: true` makes the dispose idempotent\n // (multiple cleanup paths call this on SIGINT, single-flight\n // teardown, etc.).\n await rm(dir, { recursive: true, force: true });\n },\n };\n}\n\n/**\n * Build the docker-args fragment that mounts a profile credentials file\n * into the container + sets the env vars the SDK chain needs.\n *\n * Returns an array of `docker run` args that the caller splices into its\n * own argv builder. Empty array when `file` is `undefined` (the\n * `--profile` flag was not set).\n *\n * The `:ro` mount flag is load-bearing — the container has no business\n * writing to its credentials file; a writable mount would let a\n * compromised handler tamper with the host-side temp file.\n */\nexport function buildProfileCredentialsDockerArgs(\n file: ProfileCredentialsFile | undefined\n): string[] {\n if (!file) return [];\n return [\n '-v',\n `${file.hostPath}:${file.containerPath}:ro`,\n '-e',\n `AWS_SHARED_CREDENTIALS_FILE=${file.containerPath}`,\n '-e',\n `AWS_PROFILE=${file.profileName}`,\n ];\n}\n","import { createHash } from 'node:crypto';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { runDockerStreaming } from '../utils/docker-cmd.js';\nimport { LocalInvokeBuildError } from '../utils/error-handler.js';\nimport { getLogger } from '../utils/logger.js';\nimport { isImageInLocalCache } from './ecr-puller.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Local from-source build for an AgentCore Runtime `CodeConfiguration`\n * (managed-runtime) artifact.\n *\n * Unlike the container artifact (which ships its own Dockerfile/image), a code\n * artifact is just source + an `EntryPoint` + a `Runtime`; AWS's managed\n * runtime runs the entrypoint, which self-serves the AgentCore HTTP contract\n * (`POST /invocations` + `GET /ping` on 8080) — typically via the\n * `bedrock-agentcore` SDK. We replicate that locally: generate a Dockerfile\n * for the runtime's base image, install the bundle's dependencies, and run the\n * entrypoint. The resulting container speaks the same 8080 contract, so the\n * existing HTTP client drives it unchanged.\n */\n\n/** AgentCore CodeConfiguration `Runtime` enum → local Docker base image. */\nconst RUNTIME_BASE_IMAGES: Record<string, string> = {\n PYTHON_3_10: 'public.ecr.aws/docker/library/python:3.10-slim',\n PYTHON_3_11: 'public.ecr.aws/docker/library/python:3.11-slim',\n PYTHON_3_12: 'public.ecr.aws/docker/library/python:3.12-slim',\n PYTHON_3_13: 'public.ecr.aws/docker/library/python:3.13-slim',\n PYTHON_3_14: 'public.ecr.aws/docker/library/python:3.14-slim',\n NODE_22: 'public.ecr.aws/docker/library/node:22-slim',\n};\n\n/** Runtimes this CLI can build a from-source image for. */\nexport const SUPPORTED_CODE_RUNTIMES = Object.keys(RUNTIME_BASE_IMAGES);\n\nexport interface BuildAgentCoreCodeImageOptions {\n /** Absolute path to the extracted code-bundle source dir (the cdk.out asset). */\n sourceDir: string;\n /** `CodeConfiguration.Runtime` enum value. */\n runtime: string;\n /** `CodeConfiguration.EntryPoint` argv. */\n entryPoint: string[];\n /** Drives `--platform` (AgentCore requires arm64). */\n architecture: 'x86_64' | 'arm64';\n /** Skip the build and require the deterministic tag to already be cached. */\n noBuild?: boolean;\n}\n\n/**\n * Build (or, with `noBuild`, verify) a local image for a code artifact and\n * return its tag. The generated Dockerfile is written to a temp dir and built\n * with the source dir as the context, so the cdk.out asset is never mutated.\n */\nexport async function buildAgentCoreCodeImage(\n options: BuildAgentCoreCodeImageOptions\n): Promise<string> {\n const logger = getLogger();\n const base = RUNTIME_BASE_IMAGES[options.runtime];\n if (!base) {\n throw new LocalInvokeBuildError(\n `AgentCore CodeConfiguration runtime '${options.runtime}' is not supported for local execution. ` +\n `Supported runtimes: ${SUPPORTED_CODE_RUNTIMES.join(', ')}.`\n );\n }\n\n const isNode = options.runtime.startsWith('NODE');\n const dockerfile = renderCodeDockerfile(base, options.entryPoint, isNode);\n const tag = computeCodeImageTag(\n options.sourceDir,\n options.runtime,\n options.entryPoint,\n dockerfile\n );\n const platform = options.architecture === 'x86_64' ? 'linux/amd64' : 'linux/arm64';\n\n if (options.noBuild === true) {\n logger.info(`Skipping docker build (--no-build). Verifying ${tag} is in local registry...`);\n if (!(await isImageInLocalCache(tag))) {\n throw new LocalInvokeBuildError(\n `image '${tag}' not in local registry and --no-build is set; ` +\n 'remove --no-build or run the build manually first.'\n );\n }\n return tag;\n }\n\n logger.info(\n `Building agent image from source (runtime=${options.runtime}, platform=${platform})...`\n );\n logger.debug(`Local tag: ${tag}`);\n\n const buildDir = await mkdtemp(\n join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-agentcore-code-`)\n );\n const dockerfilePath = join(buildDir, 'Dockerfile');\n try {\n await writeFile(dockerfilePath, dockerfile, 'utf-8');\n await runDockerStreaming([\n 'build',\n '--platform',\n platform,\n '--tag',\n tag,\n '--file',\n dockerfilePath,\n options.sourceDir,\n ]);\n } catch (err) {\n const stderr = (err as { stderr?: string }).stderr?.trim();\n throw new LocalInvokeBuildError(\n `docker build failed for AgentCore code artifact (${options.sourceDir})${stderr ? `: ${stderr}` : ''}`\n );\n } finally {\n await rm(buildDir, { recursive: true, force: true }).catch(() => undefined);\n }\n return tag;\n}\n\n/**\n * Render the generated Dockerfile. Dependencies are installed conditionally\n * (a bundle may vendor them or ship none), and the EntryPoint is mapped to a\n * CMD: a bare script (`app.py` / `server.js`) is run by the interpreter, while\n * an explicit launcher (e.g. `opentelemetry-instrument`) is run verbatim.\n */\nexport function renderCodeDockerfile(base: string, entryPoint: string[], isNode: boolean): string {\n const installStep = isNode\n ? 'RUN if [ -f package.json ]; then npm install --omit=dev; fi'\n : 'RUN if [ -f requirements.txt ]; then pip install --no-cache-dir -r requirements.txt; ' +\n 'elif [ -f pyproject.toml ]; then pip install --no-cache-dir .; fi';\n return (\n [\n `FROM ${base}`,\n 'WORKDIR /app',\n 'COPY . /app',\n installStep,\n 'EXPOSE 8080',\n `CMD ${JSON.stringify(toCmdArgv(entryPoint, isNode))}`,\n ].join('\\n') + '\\n'\n );\n}\n\n/**\n * Map the EntryPoint argv to a Docker CMD argv. The managed runtime execs the\n * entrypoint as the program; a bare script file is run by the language\n * interpreter (`python` / `node`), while a non-script first token (a launcher\n * already on PATH, e.g. `opentelemetry-instrument`) is run verbatim.\n */\nexport function toCmdArgv(entryPoint: string[], isNode: boolean): string[] {\n const first = entryPoint[0] ?? '';\n const isScript = isNode ? /\\.[cm]?js$/.test(first) : /\\.py$/.test(first);\n if (!isScript) return entryPoint;\n return [isNode ? 'node' : 'python', ...entryPoint];\n}\n\n/** Deterministic local tag, stable for identical source + runtime + entrypoint. */\nexport function computeCodeImageTag(\n sourceDir: string,\n runtime: string,\n entryPoint: string[],\n dockerfile: string\n): string {\n const hash = createHash('sha256')\n .update([sourceDir, runtime, entryPoint.join(' '), dockerfile].join('\\0'))\n .digest('hex')\n .slice(0, 16);\n return `${getEmbedConfig().resourceNamePrefix}-agentcore-code-${hash}`;\n}\n","import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { dirname, join, normalize, sep } from 'node:path';\nimport { unzipSync } from 'fflate';\nimport { CdkLocalError } from '../utils/error-handler.js';\nimport { getLogger } from '../utils/logger.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Download + extract a `fromS3` AgentCore CodeConfiguration bundle.\n *\n * A `fromS3` runtime points `AgentRuntimeArtifact.CodeConfiguration.Code.S3` at\n * a pre-existing S3 object (a ZIP of the agent source). `cdkl invoke-agentcore`\n * fetches it with the resolved credentials, extracts it to a temp dir, and runs\n * the SAME from-source build the `fromCodeAsset` path uses\n * ({@link buildAgentCoreCodeImage}) — so the only new surface here is the\n * download + unzip; the build + run + protocol-client path is unchanged.\n */\n\n/** Literal S3 object location of a fromS3 code bundle. */\nexport interface S3BundleLocation {\n bucket: string;\n key: string;\n versionId?: string;\n}\n\n/** Static / STS-issued credentials for the S3 download. */\nexport interface S3BundleCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n}\n\nexport interface DownloadS3BundleOptions {\n /** Region for the S3 client (resolved from `--stack-region` / `--region` / env). */\n region?: string;\n /** `--profile` to authenticate the download (ignored when `credentials` is set). */\n profile?: string;\n /** Explicit credentials (e.g. STS temp creds from `--assume-role`); win over `profile`. */\n credentials?: S3BundleCredentials;\n /** Injected object fetcher for tests — bypasses the AWS SDK entirely. */\n fetchObject?: (location: S3BundleLocation) => Promise<Uint8Array>;\n}\n\nexport interface ExtractedS3Bundle {\n /** Temp dir the bundle was extracted into (feed to the from-source build). */\n dir: string;\n /** Remove the temp dir (best-effort). */\n cleanup: () => Promise<void>;\n}\n\n/**\n * Download the bundle object, unzip it to a fresh temp dir, and return the dir\n * (plus a `cleanup`). The caller feeds `dir` to {@link buildAgentCoreCodeImage}\n * and calls `cleanup()` once the build is done.\n */\nexport async function downloadAndExtractS3Bundle(\n location: S3BundleLocation,\n options: DownloadS3BundleOptions = {}\n): Promise<ExtractedS3Bundle> {\n const ref = formatRef(location);\n getLogger().info(`Downloading fromS3 code bundle ${ref}...`);\n\n const bytes = await (options.fetchObject ?? defaultFetchObject(options))(location);\n\n let files: Record<string, Uint8Array>;\n try {\n files = unzipSync(bytes);\n } catch (err) {\n throw new CdkLocalError(\n `Failed to unzip the fromS3 code bundle ${ref}: ${err instanceof Error ? err.message : String(err)}. ` +\n `The object must be a ZIP archive of the agent source.`,\n 'LOCAL_INVOKE_AGENTCORE_S3_BUNDLE_UNZIP_FAILED'\n );\n }\n\n const dir = await mkdtemp(join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-agentcore-s3-`));\n try {\n let wrote = 0;\n for (const [name, content] of Object.entries(files)) {\n if (name.endsWith('/')) continue; // directory entry — no file content\n const dest = resolveSafeEntryPath(dir, name);\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, content);\n wrote += 1;\n }\n if (wrote === 0) {\n throw new CdkLocalError(\n `The fromS3 code bundle ${ref} contained no files.`,\n 'LOCAL_INVOKE_AGENTCORE_S3_BUNDLE_EMPTY'\n );\n }\n } catch (err) {\n await rm(dir, { recursive: true, force: true }).catch(() => undefined);\n throw err;\n }\n\n return {\n dir,\n cleanup: () => rm(dir, { recursive: true, force: true }).then(() => undefined),\n };\n}\n\nfunction formatRef(location: S3BundleLocation): string {\n const version = location.versionId ? `?versionId=${location.versionId}` : '';\n return `s3://${location.bucket}/${location.key}${version}`;\n}\n\n/**\n * Guard against zip-slip: reject an entry whose normalized path escapes the\n * extraction root (e.g. `../../etc/passwd`).\n */\nfunction resolveSafeEntryPath(root: string, entry: string): string {\n const dest = normalize(join(root, entry));\n const rootWithSep = root.endsWith(sep) ? root : root + sep;\n if (dest !== root && !dest.startsWith(rootWithSep)) {\n throw new CdkLocalError(\n `Refusing to extract a fromS3 bundle entry that escapes the target dir: '${entry}'.`,\n 'LOCAL_INVOKE_AGENTCORE_S3_BUNDLE_ZIP_SLIP'\n );\n }\n return dest;\n}\n\nfunction defaultFetchObject(\n options: DownloadS3BundleOptions\n): (location: S3BundleLocation) => Promise<Uint8Array> {\n return async (location) => {\n const { S3Client, GetObjectCommand } = await import('@aws-sdk/client-s3');\n const client = new S3Client({\n ...(options.region && { region: options.region }),\n ...(options.profile && !options.credentials && { profile: options.profile }),\n ...(options.credentials && {\n credentials: {\n accessKeyId: options.credentials.accessKeyId,\n secretAccessKey: options.credentials.secretAccessKey,\n ...(options.credentials.sessionToken && {\n sessionToken: options.credentials.sessionToken,\n }),\n },\n }),\n });\n try {\n const res = await client.send(\n new GetObjectCommand({\n Bucket: location.bucket,\n Key: location.key,\n ...(location.versionId && { VersionId: location.versionId }),\n })\n );\n if (!res.Body) {\n throw new CdkLocalError(\n `S3 GetObject for ${formatRef(location)} returned an empty body.`,\n 'LOCAL_INVOKE_AGENTCORE_S3_BUNDLE_EMPTY_BODY'\n );\n }\n return await res.Body.transformToByteArray();\n } finally {\n client.destroy();\n }\n };\n}\n","import { setTimeout as delay } from 'node:timers/promises';\n\n/**\n * HTTP client for the Bedrock AgentCore Runtime container contract.\n *\n * An AgentCore agent container listens on `0.0.0.0:8080` and exposes two\n * endpoints (AgentCore contract constants — they are NOT in the CFn\n * template):\n *\n * GET /ping → 200 + `{\"status\":\"Healthy\"|\"HealthyBusy\",...}`\n * POST /invocations → JSON or SSE response for an arbitrary JSON body\n *\n * Unlike the Lambda path there is no Runtime Interface Emulator — this is\n * plain HTTP. `cdkl invoke-agentcore` runs the container, waits for `/ping`,\n * then POSTs one event to `/invocations` (invoke-once).\n */\n\nconst PING_PATH = '/ping';\nconst INVOCATIONS_PATH = '/invocations';\n\n/**\n * Header AgentCore Runtime uses to carry the session id to the container.\n * Real AgentCore always sends it; we generate one (or pass the user's\n * `--session-id`) so agents that read it behave as they do in the cloud.\n */\nexport const AGENTCORE_SESSION_ID_HEADER = 'X-Amzn-Bedrock-AgentCore-Runtime-Session-Id';\n\nexport interface AgentCoreInvokeResult {\n /** HTTP status code of the `/invocations` response. */\n status: number;\n /** Response `Content-Type` (e.g. `application/json` or `text/event-stream`), or null. */\n contentType: string | null;\n /**\n * Response body for the buffered path — JSON or SSE passed through verbatim.\n * Empty when {@link streamed} is true (the body was delivered chunk-by-chunk\n * via {@link InvokeAgentCoreOptions.onChunk} instead of buffered).\n */\n raw: string;\n /**\n * True when a `text/event-stream` body was streamed incrementally through\n * {@link InvokeAgentCoreOptions.onChunk} (already emitted; `raw` is empty);\n * false for the buffered path.\n */\n streamed: boolean;\n}\n\n/**\n * Wait until the agent's `GET /ping` returns a 2xx on `host:port`, or\n * throw after `timeoutMs`.\n *\n * Docker's port forwarder accepts TCP as soon as `-p` binds, before the\n * agent's HTTP server is up, so a TCP probe would declare ready too early.\n * We probe `/ping` and treat only a 2xx as ready; connect/reset/abort and\n * any non-2xx status (the server is up but still warming) are retried.\n * Agent frameworks can be slow to import, so the default window is wider\n * than the Lambda RIE probe's.\n */\nexport async function waitForAgentCorePing(\n host: string,\n port: number,\n timeoutMs = 30_000\n): Promise<void> {\n const deadline = Date.now() + timeoutMs;\n let lastDetail = '';\n\n while (Date.now() < deadline) {\n try {\n const status = await pingProbe(host, port, 1000);\n if (status !== undefined) {\n if (status >= 200 && status < 300) {\n // Short settle: even after a 200, the very next request can race\n // the server on a cold daemon. Cheap insurance.\n await delay(150);\n return;\n }\n lastDetail = `last /ping status ${status}`;\n }\n } catch (err) {\n lastDetail = err instanceof Error ? err.message : String(err);\n }\n await delay(150);\n }\n\n const tail = lastDetail ? `: ${lastDetail}` : '';\n throw new Error(\n `AgentCore agent did not become ready on ${host}:${port} within ${timeoutMs}ms${tail}. ` +\n `The container may have exited early or may not serve GET ${PING_PATH} — check 'docker logs' output.`\n );\n}\n\n/**\n * Issue `GET /ping`. Returns the HTTP status on any response, undefined on\n * a transient connect/reset/abort (treated as \"not ready yet\"). Other\n * error classes (e.g. DNS) propagate.\n */\nasync function pingProbe(\n host: string,\n port: number,\n timeoutMs: number\n): Promise<number | undefined> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(`http://${host}:${port}${PING_PATH}`, {\n method: 'GET',\n signal: controller.signal,\n });\n await response.text().catch(() => undefined);\n return response.status;\n } catch (err) {\n if (isTransientNetworkError(err)) return undefined;\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * `fetch()` failures during container boot manifest as a generic\n * `TypeError: fetch failed` whose `.cause` carries the underlying Node\n * `ECONNRESET` / `ECONNREFUSED` / `UND_ERR_SOCKET`. Treat all of those —\n * plus an `AbortError` from the per-probe timeout — as \"not ready, retry\".\n */\nfunction isTransientNetworkError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (err.name === 'AbortError') return true;\n if (err.name === 'TypeError' && err.message === 'fetch failed') return true;\n const cause = (err as { cause?: { code?: string } }).cause;\n if (cause?.code === 'ECONNRESET') return true;\n if (cause?.code === 'ECONNREFUSED') return true;\n if (cause?.code === 'UND_ERR_SOCKET') return true;\n return false;\n}\n\nexport interface InvokeAgentCoreOptions {\n /** Value for the {@link AGENTCORE_SESSION_ID_HEADER} request header. */\n sessionId: string;\n /** Abort the request after this many ms. */\n timeoutMs: number;\n /**\n * Optional `Authorization` header to forward (e.g. `Bearer <jwt>`). Real\n * AgentCore forwards the validated request to the container, so agents\n * that read the header behave the same locally. Omitted when unset.\n */\n authorization?: string;\n /**\n * Optional extra headers to forward on the `/invocations` request — used by\n * the `--sigv4` path to carry the full signed-request header set\n * (`X-Amz-Date`, `X-Amz-Content-Sha256`, optional `X-Amz-Security-Token`).\n * Each entry's name is forwarded verbatim; an `Authorization` entry here\n * overrides {@link authorization}.\n */\n additionalHeaders?: Record<string, string>;\n /**\n * Sink for incremental `text/event-stream` output. When provided AND the\n * response Content-Type is `text/event-stream`, the body is decoded and\n * streamed chunk-by-chunk through this callback as it arrives — matching the\n * incremental UX AgentCore gives in the cloud — instead of being buffered.\n * The result then has `streamed: true` and an empty `raw`. For a non-SSE\n * response (or when omitted), the body is buffered into `raw` as before.\n */\n onChunk?: (text: string) => void;\n}\n\n/**\n * POST the event body to the agent's `/invocations` endpoint with the\n * session-id header and a JSON content type, then return the response.\n *\n * A `text/event-stream` response is streamed incrementally through\n * `options.onChunk` (when given) so the user sees tokens as they arrive — the\n * result is then `streamed: true` with an empty `raw`. Any other response (or\n * a missing sink) is buffered into `raw` and returned verbatim.\n */\nexport async function invokeAgentCore(\n host: string,\n port: number,\n event: unknown,\n options: InvokeAgentCoreOptions\n): Promise<AgentCoreInvokeResult> {\n const url = `http://${host}:${port}${INVOCATIONS_PATH}`;\n const body = JSON.stringify(event ?? {});\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), options.timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n [AGENTCORE_SESSION_ID_HEADER]: options.sessionId,\n ...(options.authorization && { Authorization: options.authorization }),\n ...(options.additionalHeaders ?? {}),\n },\n body,\n signal: controller.signal,\n });\n\n const contentType = response.headers.get('content-type');\n const isSse = (contentType ?? '').includes('text/event-stream');\n\n if (isSse && options.onChunk && response.body) {\n // Stream chunk-by-chunk under the SAME abort signal — an agent that\n // emits SSE frames slowly (a chat agent streaming tokens) reaches stdout\n // as it arrives instead of all at once on completion. The abort fires if\n // the whole stream exceeds `timeoutMs`.\n await streamBody(response.body, options.onChunk);\n return { status: response.status, contentType, raw: '', streamed: true };\n }\n\n // Buffer the body under the SAME abort signal — an agent that returns\n // headers but stalls mid-body would otherwise hang past `timeoutMs` since\n // `fetch` resolves on headers.\n const raw = await response.text();\n return { status: response.status, contentType, raw, streamed: false };\n } catch (err) {\n if ((err as { name?: string }).name === 'AbortError') {\n throw new Error(\n `AgentCore invoke at ${url} timed out after ${options.timeoutMs}ms. ` +\n `The agent may be hung; check container logs.`\n );\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Decode a response body stream to UTF-8 text and push each chunk to `onChunk`\n * as it arrives. Uses the reader API (portable across Node versions) and a\n * streaming TextDecoder so a multi-byte char split across chunk boundaries is\n * not corrupted.\n */\nasync function streamBody(\n body: ReadableStream<Uint8Array>,\n onChunk: (text: string) => void\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n const text = decoder.decode(value, { stream: true });\n if (text) onChunk(text);\n }\n }\n const tail = decoder.decode();\n if (tail) onChunk(tail);\n } finally {\n reader.releaseLock();\n }\n}\n","import { Sha256 } from '@aws-crypto/sha256-js';\nimport { SignatureV4 } from '@smithy/signature-v4';\nimport { AGENTCORE_SESSION_ID_HEADER } from './agentcore-client.js';\n\n/**\n * Client-side SigV4 signing for `cdkl invoke-agentcore --sigv4`.\n *\n * AgentCore's `InvokeAgentRuntime` API authenticates inbound `/invocations`\n * requests with IAM SigV4 when the runtime declares no\n * `customJwtAuthorizer`. The deployed cloud verifies the signature; a locally\n * running agent never does (no AWS public-key infra inside the container),\n * but an agent that introspects the `Authorization` header (e.g. via the\n * `bedrock-agentcore` SDK's request context) sees the same shape it would in\n * the cloud — header parity for debugging and local-dev of IAM-aware agents.\n *\n * Signing is OPT-IN via `--sigv4` on the command. Default behavior is\n * unchanged: no Authorization header on an unauthenticated invoke.\n */\n\n/** AWS service name for AgentCore InvokeAgentRuntime SigV4 signing. */\nexport const AGENTCORE_SIGV4_SERVICE = 'bedrock-agentcore';\n\n/** Static (or STS-issued) credentials for SigV4 signing. */\nexport interface SigV4Credentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n}\n\nexport interface SignAgentCoreInvocationOptions {\n credentials: SigV4Credentials;\n region: string;\n host: string;\n port: number;\n /** Path of the request being signed (e.g. `/invocations`). */\n path: string;\n /** Stringified request body the signature commits to. */\n body: string;\n /** AgentCore session id (sent as the session-id header AND part of the signed canonical request). */\n sessionId: string;\n /** HTTP method (default `POST`). */\n method?: string;\n /** `now()` for deterministic tests. Defaults to real `Date.now`. */\n now?: () => number;\n}\n\n/**\n * The headers an inbound SigV4-signed `/invocations` request carries. The\n * caller forwards every entry to the agent container; the agent sees the same\n * header set the cloud-side AgentCore would see.\n */\nexport interface SignedAgentCoreHeaders {\n /** `AWS4-HMAC-SHA256 Credential=..., SignedHeaders=..., Signature=...`. */\n authorization: string;\n /** `X-Amz-Date` ISO-8601 basic timestamp used in the signature. */\n amzDate: string;\n /** `X-Amz-Content-Sha256` payload hex digest (always sent so a body-stripping proxy can't alter it). */\n amzContentSha256: string;\n /** `X-Amz-Security-Token` — only present for STS-issued credentials. */\n amzSecurityToken?: string;\n}\n\n/**\n * Build a SigV4 signature for a `POST /invocations` request to the local\n * AgentCore container. Returns the headers that must be forwarded.\n */\nexport async function signAgentCoreInvocation(\n opts: SignAgentCoreInvocationOptions\n): Promise<SignedAgentCoreHeaders> {\n const signer = new SignatureV4({\n credentials: {\n accessKeyId: opts.credentials.accessKeyId,\n secretAccessKey: opts.credentials.secretAccessKey,\n ...(opts.credentials.sessionToken && { sessionToken: opts.credentials.sessionToken }),\n },\n region: opts.region,\n service: AGENTCORE_SIGV4_SERVICE,\n sha256: Sha256,\n });\n\n const request = {\n method: opts.method ?? 'POST',\n protocol: 'http:',\n hostname: opts.host,\n port: opts.port,\n path: opts.path,\n headers: {\n 'Content-Type': 'application/json',\n Host: `${opts.host}:${opts.port}`,\n [AGENTCORE_SESSION_ID_HEADER]: opts.sessionId,\n },\n body: opts.body,\n };\n\n const signed = await signer.sign(request, {\n ...(opts.now && { signingDate: new Date(opts.now()) }),\n });\n\n // Header keys come back in mixed case from @smithy/signature-v4. Read\n // case-insensitively so the rest of the command doesn't care.\n const get = (name: string): string | undefined => {\n const lower = name.toLowerCase();\n for (const [k, v] of Object.entries(signed.headers)) {\n if (k.toLowerCase() === lower) return v;\n }\n return undefined;\n };\n\n const authorization = get('authorization');\n const amzDate = get('x-amz-date');\n const amzContentSha256 = get('x-amz-content-sha256');\n if (!authorization || !amzDate) {\n throw new Error('SigV4 signing produced no Authorization / X-Amz-Date header — internal error');\n }\n\n const out: SignedAgentCoreHeaders = {\n authorization,\n amzDate,\n amzContentSha256: amzContentSha256 ?? '',\n };\n const amzSecurityToken = get('x-amz-security-token');\n if (amzSecurityToken) out.amzSecurityToken = amzSecurityToken;\n return out;\n}\n","import { setTimeout as delay } from 'node:timers/promises';\n\n/**\n * Client for the Bedrock AgentCore Runtime MCP protocol contract.\n *\n * An MCP-protocol AgentCore Runtime container listens on `0.0.0.0:8000` and\n * serves the Model Context Protocol over **Streamable HTTP** at `POST /mcp`\n * (no `GET /ping` — unlike the HTTP protocol). Each JSON-RPC message is its\n * own POST. `cdkl invoke-agentcore` performs the minimal session lifecycle:\n *\n * 1. `initialize` — negotiate; the server MAY return an\n * `Mcp-Session-Id` header to echo thereafter.\n * 2. `notifications/initialized` — required before any request (202, no body).\n * 3. one request — `tools/list` by default, or the method/params\n * from `--event` (e.g. `tools/call`).\n *\n * Talking to the local container is **vanilla MCP**: the\n * `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` header and the inbound OAuth\n * bearer are AgentCore managed-plane concerns the front door maps to MCP's own\n * `Mcp-Session-Id`, so a direct local client does not send them.\n */\n\n/** Container port an MCP-protocol AgentCore Runtime listens on. */\nexport const MCP_CONTAINER_PORT = 8000;\n/** HTTP path of the MCP Streamable-HTTP endpoint. */\nexport const MCP_PATH = '/mcp';\n/** MCP protocol version this client negotiates. */\nexport const MCP_PROTOCOL_VERSION = '2025-06-18';\n\nconst SESSION_ID_HEADER = 'mcp-session-id';\nconst PROTOCOL_VERSION_HEADER = 'MCP-Protocol-Version';\n\n/** A JSON-RPC request to send after the handshake (id + jsonrpc are added). */\nexport interface McpJsonRpcRequest {\n method: string;\n params?: unknown;\n}\n\nexport interface McpInvokeResult {\n /** True when the JSON-RPC response carried no top-level `error`. */\n ok: boolean;\n /** The JSON-RPC response message, pretty-printed for display. */\n raw: string;\n}\n\nexport interface McpInvokeOptions {\n /**\n * Total ms to keep retrying the initial `initialize` POST while the\n * container boots (MCP has no `/ping`, so a successful initialize IS the\n * readiness signal). Default 30s.\n */\n readyTimeoutMs?: number;\n /** Per-request abort timeout once the server is reachable. Default 120s. */\n requestTimeoutMs?: number;\n /** Injected `fetch` for tests. Defaults to the global. */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Run the MCP session lifecycle against a local container and return the\n * single request's JSON-RPC response. The initial `initialize` POST is\n * retried for `readyTimeoutMs` to absorb container boot (there is no separate\n * readiness endpoint), so this also serves as the wait-for-ready step.\n */\nexport async function mcpInvokeOnce(\n host: string,\n port: number,\n request: McpJsonRpcRequest,\n options: McpInvokeOptions = {}\n): Promise<McpInvokeResult> {\n const fetchImpl = options.fetchImpl ?? fetch;\n const url = `http://${host}:${port}${MCP_PATH}`;\n const requestTimeoutMs = options.requestTimeoutMs ?? 120_000;\n\n const sessionId = await initializeWithRetry(fetchImpl, url, options.readyTimeoutMs ?? 30_000);\n\n await postMcp(\n fetchImpl,\n url,\n { jsonrpc: '2.0', method: 'notifications/initialized' },\n sessionId,\n requestTimeoutMs\n );\n\n const result = await postMcp(\n fetchImpl,\n url,\n {\n jsonrpc: '2.0',\n id: 1,\n method: request.method,\n ...(request.params !== undefined && { params: request.params }),\n },\n sessionId,\n requestTimeoutMs\n );\n\n const message = result.message;\n const ok = !(message !== null && typeof message === 'object' && 'error' in message);\n return { ok, raw: JSON.stringify(message ?? null, null, 2) };\n}\n\n/**\n * POST `initialize`, retrying transient connect failures until the container\n * is up or `readyTimeoutMs` elapses. Returns the `Mcp-Session-Id` the server\n * assigned (undefined for a stateless server that omits it).\n */\nasync function initializeWithRetry(\n fetchImpl: typeof fetch,\n url: string,\n readyTimeoutMs: number\n): Promise<string | undefined> {\n const deadline = Date.now() + readyTimeoutMs;\n const body = {\n jsonrpc: '2.0',\n id: 0,\n method: 'initialize',\n params: {\n protocolVersion: MCP_PROTOCOL_VERSION,\n capabilities: {},\n clientInfo: { name: 'cdkl', version: '1' },\n },\n };\n let lastDetail = '';\n\n while (Date.now() < deadline) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), 5000);\n try {\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n await response.text().catch(() => undefined);\n if (response.status >= 200 && response.status < 300) {\n return response.headers.get(SESSION_ID_HEADER) ?? undefined;\n }\n // A reachable-but-non-2xx initialize is treated as \"up but not ready to\n // handle the protocol yet\" (e.g. a framework still wiring its /mcp\n // route) and retried like a connect error — mirroring how the HTTP\n // path's GET /ping retries a non-2xx while the server warms up. The last\n // status is surfaced in the readiness error if the window expires.\n lastDetail = `initialize returned HTTP ${response.status}`;\n throw new Error(lastDetail);\n } catch (err) {\n if (!isTransientNetworkError(err)) {\n // A non-transient error after the server is reachable is fatal.\n if (lastDetail) {\n throw new Error(\n `MCP initialize at ${url} failed: ${lastDetail}. Check 'docker logs' output.`\n );\n }\n throw err;\n }\n lastDetail = err instanceof Error ? err.message : String(err);\n } finally {\n clearTimeout(timer);\n }\n await delay(150);\n }\n\n throw new Error(\n `MCP server did not become ready on ${url} within ${readyTimeoutMs}ms` +\n `${lastDetail ? `: ${lastDetail}` : ''}. ` +\n `The container may have exited early or may not serve POST ${MCP_PATH} — check 'docker logs'.`\n );\n}\n\n/**\n * POST one JSON-RPC message and, for a request (one with an `id`), return the\n * parsed JSON-RPC response — handling both an `application/json` body and a\n * `text/event-stream` (the server picks either per the Streamable-HTTP spec).\n * A notification (no `id`) returns 202 with no body and yields no message.\n */\nasync function postMcp(\n fetchImpl: typeof fetch,\n url: string,\n body: { jsonrpc: string; id?: number; method: string; params?: unknown },\n sessionId: string | undefined,\n timeoutMs: number\n): Promise<{ status: number; message?: unknown }> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json, text/event-stream',\n [PROTOCOL_VERSION_HEADER]: MCP_PROTOCOL_VERSION,\n ...(sessionId && { 'Mcp-Session-Id': sessionId }),\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n const text = await response.text();\n // Notification: no response payload to parse (server returns 202).\n if (body.id === undefined) return { status: response.status };\n\n const contentType = response.headers.get('content-type') ?? '';\n const message = contentType.includes('text/event-stream')\n ? parseSseForJsonRpc(text, body.id)\n : text\n ? safeJsonParse(text)\n : undefined;\n return { status: response.status, message };\n } catch (err) {\n if ((err as { name?: string }).name === 'AbortError') {\n throw new Error(\n `MCP request '${body.method}' at ${url} timed out after ${timeoutMs}ms. ` +\n `The server may be hung; check container logs.`\n );\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Extract the JSON-RPC message matching `id` from an SSE body. Frames are\n * separated by blank lines; a frame's `data:` lines are concatenated and\n * parsed as JSON. Returns the id-matching message, else the last parseable\n * one (servers typically send a single frame carrying the response).\n */\nexport function parseSseForJsonRpc(text: string, id: number): unknown {\n let last: unknown;\n for (const frame of text.split(/\\r?\\n\\r?\\n/)) {\n const data = frame\n .split(/\\r?\\n/)\n .filter((line) => line.startsWith('data:'))\n .map((line) => line.slice('data:'.length).trimStart())\n .join('\\n');\n if (!data) continue;\n const parsed = safeJsonParse(data);\n if (parsed === undefined) continue;\n last = parsed;\n if (parsed !== null && typeof parsed === 'object' && (parsed as { id?: unknown }).id === id) {\n return parsed;\n }\n }\n return last;\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/**\n * `fetch()` failures during container boot manifest as a generic\n * `TypeError: fetch failed` whose `.cause` carries the underlying\n * `ECONNRESET` / `ECONNREFUSED` / `UND_ERR_SOCKET`; an `AbortError` is the\n * per-attempt timeout. Treat all of those — plus the synthetic non-2xx retry\n * — as \"not ready, retry\".\n */\nfunction isTransientNetworkError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (err.name === 'AbortError') return true;\n if (err.name === 'TypeError' && err.message === 'fetch failed') return true;\n if (err.message.startsWith('initialize returned HTTP')) return true;\n const cause = (err as { cause?: { code?: string } }).cause;\n if (cause?.code === 'ECONNRESET') return true;\n if (cause?.code === 'ECONNREFUSED') return true;\n if (cause?.code === 'UND_ERR_SOCKET') return true;\n return false;\n}\n","import { setTimeout as delay } from 'node:timers/promises';\n\n/**\n * Client for the Bedrock AgentCore Runtime A2A protocol contract.\n *\n * An A2A-protocol AgentCore Runtime container listens on `0.0.0.0:9000` and\n * serves the Agent2Agent JSON-RPC 2.0 contract at `POST /` (the root). Each\n * call is one JSON-RPC request and one JSON-RPC response. Unlike MCP there is\n * no session lifecycle to negotiate — the request is sent directly. `cdkl\n * invoke-agentcore` POSTs the method/params from `--event` (defaults to\n * `agent/getCard`, the agent's discovery card) and prints the response.\n *\n * Talking to the local container is **vanilla A2A**: the\n * `X-Amzn-Bedrock-AgentCore-Runtime-Session-Id` header and the inbound OAuth\n * bearer are AgentCore managed-plane concerns the front door layers on top,\n * so a direct local client does not send them.\n */\n\n/** Container port an A2A-protocol AgentCore Runtime listens on. */\nexport const A2A_CONTAINER_PORT = 9000;\n/** HTTP path of the A2A JSON-RPC endpoint. */\nexport const A2A_PATH = '/';\n\n/** A JSON-RPC request to send to an A2A agent (id + jsonrpc are added). */\nexport interface A2aJsonRpcRequest {\n method: string;\n params?: unknown;\n}\n\nexport interface A2aInvokeResult {\n /** True when the JSON-RPC response carried no top-level `error`. */\n ok: boolean;\n /** The JSON-RPC response message, pretty-printed for display. */\n raw: string;\n}\n\nexport interface A2aInvokeOptions {\n /**\n * Total ms to keep retrying the POST while the container boots (A2A has no\n * dedicated readiness endpoint, so a successful POST IS the readiness\n * signal). Default 30s.\n */\n readyTimeoutMs?: number;\n /**\n * Per-attempt abort timeout. Bounds each POST (including the user's real\n * agent call), so a legitimately slow first response isn't misreported as\n * \"not ready\". Default 120s.\n */\n requestTimeoutMs?: number;\n /** Injected `fetch` for tests. Defaults to the global. */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * Send one JSON-RPC request to a local A2A container and return the parsed\n * response. The POST is retried while the container boots (there is no\n * separate readiness endpoint), so this also serves as the wait-for-ready step.\n */\nexport async function a2aInvokeOnce(\n host: string,\n port: number,\n request: A2aJsonRpcRequest,\n options: A2aInvokeOptions = {}\n): Promise<A2aInvokeResult> {\n const fetchImpl = options.fetchImpl ?? fetch;\n const url = `http://${host}:${port}${A2A_PATH}`;\n const requestTimeoutMs = options.requestTimeoutMs ?? 120_000;\n const readyTimeoutMs = options.readyTimeoutMs ?? 30_000;\n\n const body = {\n jsonrpc: '2.0',\n id: 1,\n method: request.method,\n ...(request.params !== undefined && { params: request.params }),\n };\n\n const message = await postWithReadyRetry(fetchImpl, url, body, requestTimeoutMs, readyTimeoutMs);\n const ok = !(message !== null && typeof message === 'object' && 'error' in message);\n return { ok, raw: JSON.stringify(message ?? null, null, 2) };\n}\n\n/**\n * POST a JSON-RPC message, retrying transient connect failures + reachable-\n * but-non-2xx responses until the container is up. The retry window is\n * `readyTimeoutMs`; each attempt is bounded by `requestTimeoutMs` (the\n * per-attempt abort), which protects the user's real (potentially slow)\n * agent call without misreporting it as \"not ready\". Connect failures\n * (ECONNREFUSED) propagate immediately regardless of the abort timer, so\n * the retry cadence keeps working during boot. The loop exits once a 2xx\n * arrives (returning the parsed body) or once `readyTimeoutMs` elapses\n * (throwing the final \"did not become ready\" error below).\n */\nasync function postWithReadyRetry(\n fetchImpl: typeof fetch,\n url: string,\n body: { jsonrpc: string; id: number; method: string; params?: unknown },\n requestTimeoutMs: number,\n readyTimeoutMs: number\n): Promise<unknown> {\n const deadline = Date.now() + readyTimeoutMs;\n let lastDetail = '';\n\n while (Date.now() < deadline) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), requestTimeoutMs);\n try {\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n const text = await response.text();\n if (response.status >= 200 && response.status < 300) {\n if (!text) return undefined;\n const parsed = safeJsonParse(text);\n if (parsed === undefined) {\n // 2xx but the body isn't valid JSON — a framing error from the\n // agent, not a transient warmup state. Fail fast with the bytes\n // we saw rather than silently reporting ok=true / raw=\"null\".\n throw new Error(\n `A2A POST at ${url} returned HTTP ${response.status} with a non-JSON body: ${text.slice(0, 200)}`\n );\n }\n return parsed;\n }\n // Reachable-but-non-2xx during the readiness window is treated as\n // \"framework still wiring its / route\" (mirroring MCP); the loop\n // bails out below with this detail when the window expires.\n lastDetail = `A2A POST returned HTTP ${response.status}`;\n } catch (err) {\n if (!isTransientNetworkError(err)) throw err;\n lastDetail = err instanceof Error ? err.message : String(err);\n } finally {\n clearTimeout(timer);\n }\n await delay(150);\n }\n\n throw new Error(\n `A2A server did not become ready on ${url} within ${readyTimeoutMs}ms` +\n `${lastDetail ? `: ${lastDetail}` : ''}. ` +\n `The container may have exited early or may not serve POST ${A2A_PATH} — check 'docker logs'.`\n );\n}\n\nfunction safeJsonParse(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\nfunction isTransientNetworkError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n if (err.name === 'AbortError') return true;\n if (err.name === 'TypeError' && err.message === 'fetch failed') return true;\n const cause = (err as { cause?: { code?: string } }).cause;\n if (cause?.code === 'ECONNRESET') return true;\n if (cause?.code === 'ECONNREFUSED') return true;\n if (cause?.code === 'UND_ERR_SOCKET') return true;\n return false;\n}\n","import { WebSocket, type RawData } from 'ws';\nimport { AGENTCORE_SESSION_ID_HEADER } from './agentcore-client.js';\n\n/**\n * WebSocket client for the Bedrock AgentCore Runtime HTTP-protocol `/ws`\n * endpoint (bidirectional streaming, on the same 8080 container as\n * `POST /invocations` + `GET /ping`).\n *\n * Connect to `ws://host:8080/ws`, send the `--event` as the first frame, and\n * stream every received frame to the sink. When a {@link\n * InvokeAgentCoreWsOptions.frameSource} is supplied (the `--ws-interactive`\n * REPL path), additional frames from that async iterable are sent after the\n * initial event, and the client closes the stream when the iterable is\n * exhausted (or when the server closes first — whichever happens first). The\n * wire framing over `/ws` is agent-defined (AWS pipes bytes transparently),\n * so this mirrors that — it does not interpret the frames. The AgentCore\n * session id is sent on the upgrade as {@link AGENTCORE_SESSION_ID_HEADER},\n * the way the cloud front door does.\n */\n\nconst WS_PATH = '/ws';\n\nexport interface InvokeAgentCoreWsOptions {\n /** Value for the {@link AGENTCORE_SESSION_ID_HEADER} upgrade header. */\n sessionId: string;\n /** Sink for each received text frame, in arrival order. */\n onMessage: (text: string) => void;\n /** Abort the whole exchange (connect + stream) after this many ms. */\n timeoutMs: number;\n /**\n * `Authorization: Bearer <jwt>` to send on the upgrade when the runtime\n * declares a `customJwtAuthorizer` — forwarded the way the HTTP path\n * forwards it to `/invocations`, so an agent that reads the header behaves\n * as in the cloud.\n */\n authorization?: string;\n /**\n * Optional async iterable of additional text frames to send after the\n * initial `event`. Used by `--ws-interactive` to wire `process.stdin`\n * (line-buffered) to the WebSocket — each yielded string becomes one text\n * frame. The connection is closed gracefully when the iterable is\n * exhausted; if the server closes first, iteration is stopped via the\n * iterator's `return()` method. Errors thrown by the iterable propagate as\n * the function's rejection.\n */\n frameSource?: AsyncIterable<string>;\n /** Injected WebSocket implementation for tests. Defaults to `ws`. */\n webSocketImpl?: typeof WebSocket;\n}\n\nexport interface AgentCoreWsResult {\n /** Number of frames streamed from the agent before it closed. */\n frames: number;\n}\n\n/**\n * Open `/ws`, send the event as the first frame, stream received frames to\n * `onMessage`, and resolve when the server closes. Rejects on a connection\n * error or when `timeoutMs` elapses before the server closes.\n */\nexport async function invokeAgentCoreWs(\n host: string,\n port: number,\n event: unknown,\n options: InvokeAgentCoreWsOptions\n): Promise<AgentCoreWsResult> {\n const Impl = options.webSocketImpl ?? WebSocket;\n const url = `ws://${host}:${port}${WS_PATH}`;\n const body = JSON.stringify(event ?? {});\n\n return new Promise<AgentCoreWsResult>((resolve, reject) => {\n const ws = new Impl(url, {\n headers: {\n [AGENTCORE_SESSION_ID_HEADER]: options.sessionId,\n ...(options.authorization && { Authorization: options.authorization }),\n },\n });\n let frames = 0;\n let settled = false;\n\n const finish = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n fn();\n };\n\n const timer = setTimeout(() => {\n finish(() => {\n stopIterator();\n try {\n ws.terminate();\n } catch {\n /* already closing */\n }\n reject(\n new Error(\n `AgentCore /ws at ${url} timed out after ${options.timeoutMs}ms. ` +\n `The agent may be hung or may not close the stream; check container logs.`\n )\n );\n });\n }, options.timeoutMs);\n\n let iterator: AsyncIterator<string> | undefined;\n const stopIterator = (): void => {\n if (iterator?.return) {\n try {\n // Fire-and-forget: a return() that rejects is fine, we are tearing down.\n void iterator.return();\n } catch {\n /* iterator already exhausted */\n }\n }\n iterator = undefined;\n };\n\n ws.on('open', () => {\n ws.send(body);\n if (!options.frameSource) return;\n // Kick off the additional-frames pump in the background. Each yielded\n // string becomes one text frame; when the iterable is exhausted we close\n // the WS gracefully so the agent sees a clean close.\n void (async (): Promise<void> => {\n try {\n iterator = options.frameSource![Symbol.asyncIterator]();\n while (!settled) {\n const next = await iterator.next();\n if (settled || next.done) break;\n // `ws.send` is async via the OS socket buffer; the callback is the\n // only way to surface a queue write error before the next iteration.\n await new Promise<void>((res, rej) => {\n ws.send(next.value, (err) => (err ? rej(err) : res()));\n });\n }\n if (!settled) ws.close();\n } catch (err) {\n finish(() => {\n try {\n ws.terminate();\n } catch {\n /* already closing */\n }\n reject(err instanceof Error ? err : new Error(String(err)));\n });\n }\n })();\n });\n ws.on('message', (data: RawData) => {\n frames += 1;\n // `ws` delivers a Buffer by default (binaryType 'nodebuffer'); handle the\n // fragments / ArrayBuffer shapes too so a frame is always decoded as UTF-8.\n const buf = Buffer.isBuffer(data)\n ? data\n : Array.isArray(data)\n ? Buffer.concat(data)\n : Buffer.from(data);\n options.onMessage(buf.toString('utf-8'));\n });\n ws.on('close', () => {\n finish(() => {\n stopIterator();\n resolve({ frames });\n });\n });\n ws.on('error', (err: Error) => {\n finish(() => {\n stopIterator();\n reject(err);\n });\n });\n });\n}\n","import type { ServerResponse } from 'node:http';\nimport type { CloudFormationTemplate } from '../types/resource.js';\n\n/**\n * CORS preflight (OPTIONS) interception for `cdkl start-api`\n * (PR 8c, issue #235).\n *\n * Background: PR 8a left CORS preflight unimplemented. AWS's HTTP API\n * (`AWS::ApiGatewayV2::Api.CorsConfiguration`) responds to OPTIONS\n * preflight requests automatically — the request never reaches the\n * Lambda integration. cdk-local's local server pre-PR forwarded OPTIONS to\n * the route's handler, which usually 404s or returns a non-CORS body.\n *\n * Scope (locked in the issue brief):\n *\n * - **HTTP API v2** (this module): read `CorsConfiguration` from each\n * `AWS::ApiGatewayV2::Api` resource and intercept OPTIONS preflight\n * requests for routes on that API.\n * - **REST v1 MOCK preflight** (route-discovery.ts, NOT this module):\n * CDK's `defaultCorsPreflightOptions` synthesizes an OPTIONS\n * `AWS::ApiGateway::Method` with a MOCK integration whose literal\n * `method.response.header.*` `ResponseParameters` carry the CORS\n * headers. `route-discovery.ts` captures those at boot as\n * `DiscoveredRoute.mockCors` and the HTTP server returns the\n * captured status + headers directly on OPTIONS (no Lambda invoke,\n * no VTL evaluation). The \"REST v1 CORS is out of scope\" comment\n * that used to live here is no longer accurate — only non-CORS\n * MOCK integrations (custom VTL response bodies, MOCK on non-OPTIONS\n * methods) remain unimplemented.\n * - **Skip preflight handling when the route has an explicit OPTIONS\n * method registered** — that signals the user's Lambda owns it.\n *\n * Algorithm:\n *\n * 1. Build a per-API `CorsConfig | undefined` map at server boot\n * (`buildCorsConfigByApiId`). Routes attached to APIs that don't\n * have CorsConfiguration get nothing.\n * 2. On every incoming OPTIONS request the server first calls\n * `matchPreflight(req, configByRoute)`. If the request matches a\n * route AND its API has a CorsConfig AND there is no explicit\n * OPTIONS route, return the canonical preflight response. Otherwise\n * return `null` and let the normal request handler run.\n *\n * Validation (matches AWS's HTTP API v2 behavior closely enough for a\n * local emulator):\n *\n * - `Origin` matches `AllowOrigins` literally OR `'*'` is present.\n * - `Access-Control-Request-Method` matches `AllowMethods` literally\n * OR `'*'` is present.\n * - `Access-Control-Request-Headers` is split on `,`, every entry must\n * match `AllowHeaders` (case-insensitive) OR `'*'` must be present.\n *\n * On match, respond with `204 No Content` and the canonical headers:\n *\n * - `access-control-allow-origin`: the request's literal Origin (when\n * a wildcard hit) or the matched literal entry.\n * - `access-control-allow-methods`: literal echo of the matched method.\n * - `access-control-allow-headers`: literal echo of the matched headers\n * (or `'*'` when AWS allows-wildcard).\n * - `access-control-max-age`: when `MaxAge` is set on the config.\n * - `access-control-allow-credentials`: `'true'` when `AllowCredentials`.\n * - `access-control-expose-headers`: when `ExposeHeaders` is set.\n *\n * Mismatched preflight returns `null` (let the request fall through to\n * the route handler / 404), matching what AWS does — it returns a 4xx\n * with no CORS headers, the browser then refuses the actual request.\n */\n\n/**\n * Normalized CorsConfiguration after extraction from the template.\n * Field names match the CFn property casing (PascalCase) so test\n * fixtures can be written verbatim against CDK's synthesized output.\n */\nexport interface CorsConfig {\n AllowOrigins: string[];\n AllowMethods: string[];\n AllowHeaders: string[];\n ExposeHeaders: string[];\n MaxAge?: number;\n AllowCredentials?: boolean;\n}\n\n/**\n * Build a `logicalId → CorsConfig | undefined` map. Walks the template\n * once and picks two CORS-bearing resource types:\n *\n * - `AWS::ApiGatewayV2::Api` → `Properties.CorsConfiguration`\n * (HTTP API v2; the original PR 8c surface)\n * - `AWS::Lambda::Url` → `Properties.Cors` (Function URL; issue #644)\n *\n * Both blocks are field-for-field identical in CFn schema (same\n * `AllowOrigins` / `AllowMethods` / `AllowHeaders` / `ExposeHeaders` /\n * `MaxAge` / `AllowCredentials`), so a single parser handles both. The\n * map key is the resource's own logical ID — that ID is later looked up\n * against `DiscoveredRoute.apiLogicalId` (set to the surface-bearing\n * resource at route-discovery time) so the preflight interceptor finds\n * the right config.\n *\n * Resources without a CORS block (or whose block is malformed) are NOT\n * entered into the map.\n */\nexport function buildCorsConfigByApiId(template: CloudFormationTemplate): Map<string, CorsConfig> {\n const out = new Map<string, CorsConfig>();\n const resources = template.Resources ?? {};\n for (const [logicalId, resource] of Object.entries(resources)) {\n let raw: unknown;\n if (resource.Type === 'AWS::ApiGatewayV2::Api') {\n raw = (resource.Properties ?? {})['CorsConfiguration'];\n } else if (resource.Type === 'AWS::Lambda::Url') {\n raw = (resource.Properties ?? {})['Cors'];\n } else {\n continue;\n }\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue;\n const parsed = parseCorsConfiguration(raw as Record<string, unknown>);\n if (parsed) out.set(logicalId, parsed);\n }\n return out;\n}\n\n/**\n * Parse a single `CorsConfiguration` block. Accepts the CFn shape\n * (`AllowOrigins`, ...) — CDK's `aws-cdk-lib/aws-apigatewayv2` emits\n * this casing. Returns `undefined` when every field is missing /\n * malformed (no point installing an empty interceptor).\n */\nfunction parseCorsConfiguration(raw: Record<string, unknown>): CorsConfig | undefined {\n const allowOrigins = pickStringArray(raw['AllowOrigins']);\n const allowMethods = pickStringArray(raw['AllowMethods']);\n const allowHeaders = pickStringArray(raw['AllowHeaders']);\n const exposeHeaders = pickStringArray(raw['ExposeHeaders']);\n const maxAgeRaw = raw['MaxAge'];\n const allowCreds = raw['AllowCredentials'];\n\n // Avoid installing an interceptor when nothing was configured. A\n // CorsConfiguration block with all fields empty / unset is the same\n // as having no configuration at all.\n if (\n allowOrigins.length === 0 &&\n allowMethods.length === 0 &&\n allowHeaders.length === 0 &&\n exposeHeaders.length === 0 &&\n maxAgeRaw === undefined &&\n allowCreds === undefined\n ) {\n return undefined;\n }\n\n const config: CorsConfig = {\n AllowOrigins: allowOrigins,\n AllowMethods: allowMethods,\n AllowHeaders: allowHeaders,\n ExposeHeaders: exposeHeaders,\n };\n if (typeof maxAgeRaw === 'number' && Number.isFinite(maxAgeRaw)) {\n config.MaxAge = Math.trunc(maxAgeRaw);\n }\n if (typeof allowCreds === 'boolean') {\n config.AllowCredentials = allowCreds;\n }\n return config;\n}\n\n/**\n * Coerce an unknown into a `string[]`, dropping non-string entries.\n * Returns `[]` when the input isn't an array.\n */\nfunction pickStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n const out: string[] = [];\n for (const v of value) if (typeof v === 'string') out.push(v);\n return out;\n}\n\n/**\n * Build a `fnUrlLogicalId → CorsConfig` map by tracing CloudFront →\n * Function URL chains in the template (issue #646).\n *\n * Production-correct CDK pattern: Function URL fronted by a CloudFront\n * Distribution where CORS is declared on the CloudFront\n * `ResponseHeadersPolicy` (NOT on the Function URL itself). Without this\n * helper, `cdkl start-api` sees `Cors: null` on the Function URL\n * and emits no preflight headers — even though the CDK code correctly\n * declares the allowed origins on the CloudFront side.\n *\n * Detection: an `AWS::CloudFront::Distribution` whose `Origins[].DomainName`\n * matches the canonical CDK 2.x shape\n * `Fn::Select[2, Fn::Split['/', Fn::GetAtt[<FnUrlLogicalId>, 'FunctionUrl']]]`\n * is the chain marker. For each such origin, we walk every cache behavior\n * (`DefaultCacheBehavior` + `CacheBehaviors[]`), resolve their\n * `ResponseHeadersPolicyId: { Ref: <RhpLogicalId> }` to the\n * `AWS::CloudFront::ResponseHeadersPolicy` resource, and extract its\n * `Properties.ResponseHeadersPolicyConfig.CorsConfig`.\n *\n * Schema mapping (CloudFront → internal `CorsConfig`):\n *\n * AccessControlAllowOrigins.Items → AllowOrigins\n * AccessControlAllowMethods.Items → AllowMethods\n * AccessControlAllowHeaders.Items → AllowHeaders\n * AccessControlExposeHeaders.Items → ExposeHeaders\n * AccessControlMaxAgeSec → MaxAge\n * AccessControlAllowCredentials → AllowCredentials\n * (OriginOverride is ignored — cdk-local has only one config slot)\n *\n * Multiple distributions fronting the same Function URL: last write\n * wins (rare in practice). Per-path CORS via `CacheBehaviors[]` is\n * NOT supported in v1 — the `DefaultCacheBehavior`'s policy applies\n * to all paths.\n */\nexport function buildCorsConfigFromCloudFrontChain(\n template: CloudFormationTemplate\n): Map<string, CorsConfig> {\n const out = new Map<string, CorsConfig>();\n const resources = template.Resources ?? {};\n for (const [, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::CloudFront::Distribution') continue;\n const distConfig = (resource.Properties ?? {})['DistributionConfig'];\n if (!distConfig || typeof distConfig !== 'object') continue;\n const dc = distConfig as Record<string, unknown>;\n\n const origins = Array.isArray(dc['Origins']) ? (dc['Origins'] as unknown[]) : [];\n for (const origin of origins) {\n if (!origin || typeof origin !== 'object') continue;\n const fnUrlLogicalId = pickFnUrlLogicalIdFromOriginDomainName(\n (origin as Record<string, unknown>)['DomainName']\n );\n if (!fnUrlLogicalId) continue;\n\n // Walk every cache behavior (default + per-path) and merge any\n // CORS configs found. v1: last-write-wins on collision.\n const cacheBehaviors: unknown[] = [\n dc['DefaultCacheBehavior'],\n ...(Array.isArray(dc['CacheBehaviors']) ? (dc['CacheBehaviors'] as unknown[]) : []),\n ];\n for (const behavior of cacheBehaviors) {\n if (!behavior || typeof behavior !== 'object') continue;\n const rhpId = pickRhpRefLogicalId(\n (behavior as Record<string, unknown>)['ResponseHeadersPolicyId']\n );\n if (!rhpId) continue;\n const rhpResource = resources[rhpId];\n if (!rhpResource || rhpResource.Type !== 'AWS::CloudFront::ResponseHeadersPolicy') continue;\n const rhpConfig = (rhpResource.Properties ?? {})['ResponseHeadersPolicyConfig'];\n if (!rhpConfig || typeof rhpConfig !== 'object') continue;\n const corsConfig = (rhpConfig as Record<string, unknown>)['CorsConfig'];\n if (!corsConfig || typeof corsConfig !== 'object' || Array.isArray(corsConfig)) continue;\n const parsed = parseCloudFrontCorsConfig(corsConfig as Record<string, unknown>);\n if (parsed) out.set(fnUrlLogicalId, parsed);\n }\n }\n }\n return out;\n}\n\n/**\n * Determine whether a Function URL (`AWS::Lambda::Url`, identified by its\n * logical id) is fronted by a CloudFront Distribution origin that uses\n * Origin Access Control (OAC) to SIGN origin requests.\n *\n * Production-correct CDK pattern (`FunctionUrlOrigin.withOriginAccessControl`):\n * the Function URL declares `AuthType: AWS_IAM`, but the END client never\n * signs as the IAM principal — CloudFront re-signs the origin request with\n * its own SigV4 credentials (service `lambda`) via the OAC, and the Function\n * URL's auto-generated resource policy trusts `cloudfront.amazonaws.com`.\n * Locally there is no CloudFront in the path, so no client signature can\n * reproduce CloudFront's. Callers use this to keep these Function URLs in\n * warn-and-pass mode even when `--strict-sigv4` is set.\n *\n * Detection: a CloudFront origin whose `DomainName` matches the canonical\n * `Fn::GetAtt[<fnUrlLogicalId>, 'FunctionUrl']` chain (see\n * {@link pickFnUrlLogicalIdFromOriginDomainName}) AND carries an\n * `OriginAccessControlId`. When that id resolves to an\n * `AWS::CloudFront::OriginAccessControl` whose `SigningBehavior` is\n * explicitly `never`, CloudFront does NOT sign — so we do NOT relax (the\n * AWS_IAM + never-sign combination is non-functional in production too).\n * Any other signing behavior (`always` — the CDK default — or `no-override`)\n * counts as OAC-fronted. An `OriginAccessControlId` that can't be resolved\n * to a local resource (imported literal id) also counts — its presence on a\n * Function URL origin is the signal.\n */\nexport function isFunctionUrlOacFronted(\n template: CloudFormationTemplate,\n fnUrlLogicalId: string\n): boolean {\n const resources = template.Resources ?? {};\n for (const [, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::CloudFront::Distribution') continue;\n const distConfig = (resource.Properties ?? {})['DistributionConfig'];\n if (!distConfig || typeof distConfig !== 'object') continue;\n const origins = Array.isArray((distConfig as Record<string, unknown>)['Origins'])\n ? ((distConfig as Record<string, unknown>)['Origins'] as unknown[])\n : [];\n for (const origin of origins) {\n if (!origin || typeof origin !== 'object') continue;\n const o = origin as Record<string, unknown>;\n if (pickFnUrlLogicalIdFromOriginDomainName(o['DomainName']) !== fnUrlLogicalId) continue;\n const oacRef = o['OriginAccessControlId'];\n if (oacRef === undefined || oacRef === '') continue;\n const oacLogicalId = pickOacRefLogicalId(oacRef);\n // Unresolvable reference (imported literal id) still counts.\n if (!oacLogicalId) return true;\n const oac = resources[oacLogicalId];\n if (!oac || oac.Type !== 'AWS::CloudFront::OriginAccessControl') return true;\n const oacConfig = (oac.Properties ?? {})['OriginAccessControlConfig'];\n const signingBehavior =\n oacConfig && typeof oacConfig === 'object'\n ? (oacConfig as Record<string, unknown>)['SigningBehavior']\n : undefined;\n if (signingBehavior === 'never') continue;\n return true;\n }\n }\n return false;\n}\n\n/**\n * Unwrap an origin's `OriginAccessControlId` to the referenced\n * `AWS::CloudFront::OriginAccessControl` logical id. CDK synthesizes this\n * as `{ \"Fn::GetAtt\": [<id>, \"Id\"] }`; `{ Ref: <id> }` is also accepted.\n * Returns undefined for a literal id string (imported OAC) or any other\n * shape.\n */\nfunction pickOacRefLogicalId(value: unknown): string | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const obj = value as Record<string, unknown>;\n const ref = obj['Ref'];\n if (typeof ref === 'string' && ref.length > 0) return ref;\n const getAtt = obj['Fn::GetAtt'];\n if (Array.isArray(getAtt) && getAtt.length === 2 && typeof getAtt[0] === 'string') {\n return getAtt[0];\n }\n return undefined;\n}\n\n/**\n * Detect the canonical CDK 2.x `DomainName` shape that points a\n * CloudFront Origin at a Function URL:\n * {Fn::Select: [2, {Fn::Split: ['/', {Fn::GetAtt: [<id>, 'FunctionUrl']}]}]}\n * Returns the Function URL's logical ID, or undefined if the shape\n * doesn't match.\n */\nfunction pickFnUrlLogicalIdFromOriginDomainName(value: unknown): string | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const outer = value as Record<string, unknown>;\n const sel = outer['Fn::Select'];\n if (!Array.isArray(sel) || sel.length !== 2 || sel[0] !== 2) return undefined;\n const split = sel[1];\n if (!split || typeof split !== 'object') return undefined;\n const splitArgs = (split as Record<string, unknown>)['Fn::Split'];\n if (!Array.isArray(splitArgs) || splitArgs.length !== 2 || splitArgs[0] !== '/') return undefined;\n const getAtt = splitArgs[1];\n if (!getAtt || typeof getAtt !== 'object') return undefined;\n const ga = (getAtt as Record<string, unknown>)['Fn::GetAtt'];\n if (\n !Array.isArray(ga) ||\n ga.length !== 2 ||\n typeof ga[0] !== 'string' ||\n ga[1] !== 'FunctionUrl'\n ) {\n return undefined;\n }\n return ga[0];\n}\n\n/**\n * Unwrap a `ResponseHeadersPolicyId` value to its referenced logical\n * ID. CDK 2.x synthesizes this as `{ Ref: <id> }`. Returns undefined\n * for the AWS-managed-policy ID form (literal UUID string) since\n * cdk-local can't fetch those — and for any non-Ref shape.\n */\nfunction pickRhpRefLogicalId(value: unknown): string | undefined {\n if (!value || typeof value !== 'object') return undefined;\n const ref = (value as Record<string, unknown>)['Ref'];\n if (typeof ref !== 'string' || ref.length === 0) return undefined;\n return ref;\n}\n\n/**\n * Parse a CloudFront `ResponseHeadersPolicyConfig.CorsConfig` block\n * into the internal `CorsConfig` shape. Schema differs from Function\n * URL / HTTP API v2 (`AccessControl*` prefix + nested `Items` wrapper);\n * see `buildCorsConfigFromCloudFrontChain` JSDoc for the field mapping.\n *\n * Returns undefined when every value-bearing field is missing.\n */\nfunction parseCloudFrontCorsConfig(raw: Record<string, unknown>): CorsConfig | undefined {\n const allowOrigins = pickItemsStringArray(raw['AccessControlAllowOrigins']);\n const allowMethods = pickItemsStringArray(raw['AccessControlAllowMethods']);\n const allowHeaders = pickItemsStringArray(raw['AccessControlAllowHeaders']);\n const exposeHeaders = pickItemsStringArray(raw['AccessControlExposeHeaders']);\n const maxAgeRaw = raw['AccessControlMaxAgeSec'];\n const allowCreds = raw['AccessControlAllowCredentials'];\n\n if (\n allowOrigins.length === 0 &&\n allowMethods.length === 0 &&\n allowHeaders.length === 0 &&\n exposeHeaders.length === 0 &&\n maxAgeRaw === undefined &&\n allowCreds === undefined\n ) {\n return undefined;\n }\n\n const config: CorsConfig = {\n AllowOrigins: allowOrigins,\n AllowMethods: allowMethods,\n AllowHeaders: allowHeaders,\n ExposeHeaders: exposeHeaders,\n };\n if (typeof maxAgeRaw === 'number' && Number.isFinite(maxAgeRaw)) {\n config.MaxAge = Math.trunc(maxAgeRaw);\n }\n if (typeof allowCreds === 'boolean') {\n config.AllowCredentials = allowCreds;\n }\n return config;\n}\n\n/**\n * CloudFront `AccessControl*Origins/Methods/Headers` use a nested\n * `Items: string[]` wrapper. Unwrap to a plain `string[]`.\n */\nfunction pickItemsStringArray(value: unknown): string[] {\n if (!value || typeof value !== 'object') return [];\n const items = (value as Record<string, unknown>)['Items'];\n return pickStringArray(items);\n}\n\n/**\n * The result of a successful preflight match. The HTTP server writes\n * `statusCode + headers` and ends the response with no body.\n */\nexport interface PreflightResponse {\n statusCode: number;\n headers: Record<string, string>;\n}\n\n/**\n * Try to match an OPTIONS preflight request against the given CORS\n * config. Returns the canonical response when every check passes;\n * `null` when the request didn't satisfy AllowOrigins / AllowMethods /\n * AllowHeaders (the caller falls back to normal route dispatch — which\n * usually 404s — matching what AWS does on mismatched preflight).\n */\nexport function matchPreflight(\n req: { method: string; headers: Record<string, string[]> },\n config: CorsConfig\n): PreflightResponse | null {\n if (req.method.toUpperCase() !== 'OPTIONS') return null;\n\n const headersLower: Record<string, string> = {};\n for (const [name, values] of Object.entries(req.headers)) {\n if (values.length === 0) continue;\n headersLower[name.toLowerCase()] = values.join(',');\n }\n\n const origin = headersLower['origin'];\n const requestedMethod = headersLower['access-control-request-method'];\n if (!origin || !requestedMethod) {\n // Not a CORS preflight (just a plain OPTIONS); let the route handler\n // own it — most user code returns 200 / a documentation response.\n return null;\n }\n\n // AllowOrigins match.\n const originMatch = matchOrigin(origin, config.AllowOrigins);\n if (!originMatch) return null;\n\n // AllowMethods match.\n const methodMatch = matchToken(requestedMethod, config.AllowMethods);\n if (!methodMatch) return null;\n\n // AllowHeaders match. The request's `Access-Control-Request-Headers`\n // is a `,`-separated list — every entry must be allowed.\n const requestedHeaders = headersLower['access-control-request-headers'] ?? '';\n if (!matchHeaderList(requestedHeaders, config.AllowHeaders)) return null;\n\n // Build the response.\n const responseHeaders: Record<string, string> = {\n 'access-control-allow-origin': originMatch === '*' ? '*' : origin,\n 'access-control-allow-methods': methodMatch === '*' ? requestedMethod : methodMatch,\n };\n if (requestedHeaders.length > 0) {\n responseHeaders['access-control-allow-headers'] = requestedHeaders;\n } else if (config.AllowHeaders.length > 0 && !config.AllowHeaders.includes('*')) {\n responseHeaders['access-control-allow-headers'] = config.AllowHeaders.join(',');\n }\n if (config.ExposeHeaders.length > 0) {\n responseHeaders['access-control-expose-headers'] = config.ExposeHeaders.join(',');\n }\n if (config.MaxAge !== undefined) {\n responseHeaders['access-control-max-age'] = String(config.MaxAge);\n }\n if (config.AllowCredentials === true) {\n // RFC 6749 / browser fetch spec: when credentials are allowed, the\n // `Access-Control-Allow-Origin` MUST be a literal — `*` is invalid.\n // If config has `*` AND `AllowCredentials: true` (which AWS rejects\n // at deploy but a hand-rolled CFn template might still synthesize),\n // we echo the request Origin to keep the response valid.\n responseHeaders['access-control-allow-origin'] = origin;\n responseHeaders['access-control-allow-credentials'] = 'true';\n }\n\n // Vary: Origin — set whenever the response's `Access-Control-\n // Allow-Origin` was DERIVED from the request (wildcard match echoed\n // as `*`, literal Origin echo, or AllowCredentials echo). Without\n // this, downstream caches (browsers / CDN) may share a cached\n // response across origins and serve the wrong CORS headers to a\n // different origin — silently breaking the security model. Mirrors\n // `Vary: Origin` semantics from MDN's CORS guide and most server-side\n // CORS libraries.\n responseHeaders['vary'] = 'Origin';\n\n return { statusCode: 204, headers: responseHeaders };\n}\n\n/**\n * Apply CORS headers to an **actual** (non-preflight) response. CORS\n * spec requires that 2xx / 4xx / 5xx responses all carry\n * `Access-Control-Allow-Origin` (only preflight responses also need\n * `Allow-Methods` / `Allow-Headers` / `Max-Age`) — without it the\n * browser blocks the response body from JS regardless of status code.\n *\n * Looks up the route's `apiLogicalId` in `corsConfigByApiId`. When a\n * matching config + the request Origin satisfies `AllowOrigins`, sets:\n *\n * - `Access-Control-Allow-Origin: <origin or *>` (always)\n * - `Vary: Origin` (when origin echoed)\n * - `Access-Control-Allow-Credentials: true` (when configured)\n * - `Access-Control-Expose-Headers: <list>` (when configured)\n *\n * `Allow-Methods` / `Allow-Headers` / `Max-Age` are preflight-only and\n * deliberately NOT set on actual responses.\n *\n * Caller must invoke this BEFORE writing the response body (otherwise\n * `res.headersSent` flips and `setHeader` becomes a no-op). Idempotent:\n * a second call with the same args overwrites the same headers.\n *\n * No-op when:\n * - `route.apiLogicalId` is undefined (no surface-config-bearing\n * resource — e.g. routes discovered before issue #644's apiLogicalId\n * plumbing existed; harmless on routes without CORS to apply)\n * - The route's API has no entry in `corsConfigByApiId`\n * - The request has no `Origin` header (non-CORS request — same\n * posture as the matchPreflight gate)\n * - The Origin is not in the AllowOrigins list (browser will block\n * anyway; we don't smuggle through unauthorized origins by accident)\n */\nexport function applyCorsResponseHeaders(\n res: ServerResponse,\n apiLogicalId: string | undefined,\n corsConfigByApiId: Map<string, CorsConfig>,\n requestOrigin: string | undefined\n): void {\n if (!apiLogicalId) return;\n const cors = corsConfigByApiId.get(apiLogicalId);\n if (!cors) return;\n if (!requestOrigin) return;\n const originMatch = matchOrigin(requestOrigin, cors.AllowOrigins);\n if (!originMatch) return;\n\n // RFC 6749 / fetch spec: `Allow-Origin: *` is invalid alongside\n // `Allow-Credentials: true`. When credentials allowed AND the config\n // had `*`, echo the request Origin instead.\n const allowOrigin = originMatch === '*' && cors.AllowCredentials !== true ? '*' : requestOrigin;\n res.setHeader('Access-Control-Allow-Origin', allowOrigin);\n\n if (allowOrigin !== '*') {\n // The response varies by Origin; without `Vary: Origin` a shared\n // cache (browser / CDN) might serve the wrong-origin cached copy\n // and break the security model. Append to existing Vary if any.\n const existing = res.getHeader('Vary');\n if (typeof existing === 'string' && existing.length > 0) {\n const tokens = existing.split(',').map((t) => t.trim());\n if (!tokens.some((t) => t.toLowerCase() === 'origin')) {\n res.setHeader('Vary', `${existing}, Origin`);\n }\n } else {\n res.setHeader('Vary', 'Origin');\n }\n }\n\n if (cors.AllowCredentials === true) {\n res.setHeader('Access-Control-Allow-Credentials', 'true');\n }\n if (cors.ExposeHeaders.length > 0) {\n res.setHeader('Access-Control-Expose-Headers', cors.ExposeHeaders.join(','));\n }\n}\n\n/**\n * Whether the request's Origin matches the AllowOrigins list. Returns\n * `'*'` when a wildcard matched, the literal entry on a literal match,\n * `null` otherwise. The literal case is used by the caller to decide\n * whether to echo the request Origin or return the configured value.\n *\n * AWS's HTTP API v2 supports literal entries and the `'*'` wildcard;\n * regex / glob origins are NOT supported by AWS, so we don't either.\n */\nfunction matchOrigin(requestOrigin: string, allowOrigins: string[]): string | null {\n if (allowOrigins.length === 0) return null;\n if (allowOrigins.includes('*')) return '*';\n for (const allowed of allowOrigins) {\n if (allowed === requestOrigin) return allowed;\n }\n return null;\n}\n\n/**\n * Whether `token` matches any entry in `allowed` (case-insensitive for\n * methods + the AllowHeaders list). Returns the literal entry on match\n * or `'*'` when the wildcard hit, `null` otherwise.\n */\nfunction matchToken(token: string, allowed: string[]): string | null {\n if (allowed.length === 0) return null;\n if (allowed.includes('*')) return '*';\n const lower = token.toLowerCase();\n for (const a of allowed) {\n if (a.toLowerCase() === lower) return a;\n }\n return null;\n}\n\n/**\n * Whether every `,`-separated entry in `headerList` is allowed.\n * Empty `headerList` is always allowed (the request didn't ask for any\n * specific headers — common when the actual request has no custom\n * headers). An empty entry within a non-empty list (e.g.\n * `\"Content-Type,,,Authorization\"`) is treated as a malformed request\n * and rejected — matches AWS's stricter validation on `Access-Control-\n * Request-Headers`. Pre-fix the empty entries were silently skipped,\n * which made `\"Content-Type,,,Authorization\"` match against an\n * AllowHeaders list that only contains those two — surprising and\n * inconsistent with the docstring's \"every entry must be allowed\".\n */\nfunction matchHeaderList(headerList: string, allowed: string[]): boolean {\n const trimmed = headerList.trim();\n if (trimmed.length === 0) return true;\n if (allowed.includes('*')) return true;\n if (allowed.length === 0) return false;\n const allowedLower = new Set(allowed.map((s) => s.toLowerCase()));\n for (const entry of trimmed.split(',')) {\n const e = entry.trim().toLowerCase();\n if (e.length === 0) return false;\n if (!allowedLower.has(e)) return false;\n }\n return true;\n}\n","import type { StackInfo } from '../synthesis/assembly-reader.js';\nimport type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { RouteDiscoveryError } from '../utils/error-handler.js';\nimport { getLogger } from '../utils/logger.js';\nimport { stringifyValue } from '../utils/stringify.js';\nimport { isFunctionUrlOacFronted } from './cors-handler.js';\nimport { resolveLambdaArnIntrinsic as resolveLambdaArnShared } from './intrinsic-lambda-arn.js';\nimport { pickRefLogicalId } from './intrinsic-utils.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Authorizer detection for `cdkl start-api` (PR 8b of #224).\n *\n * The route-discovery layer now collects an optional {@link AuthorizerInfo}\n * for each route whose `AuthorizationType` references an authorizer\n * resource in the same stack. The HTTP server consults this map at\n * request time and gates the route forwarding on the authorizer's\n * verdict.\n *\n * Supported authorizer kinds:\n * - **Lambda TOKEN** (REST v1 only) — `AWS::ApiGateway::Authorizer.Type === 'TOKEN'`.\n * Identity is the single header named in `IdentitySource` (default\n * `method.request.header.Authorization`).\n * - **Lambda REQUEST** — REST v1 (`Type === 'REQUEST'`) and HTTP v2\n * (`AWS::ApiGatewayV2::Authorizer.AuthorizerType === 'REQUEST'`).\n * Identity is a comma-separated list of `method.request.header.X` /\n * `method.request.querystring.X` selectors (REST v1) or a list of\n * `$request.header.X` / `$request.querystring.X` selectors (HTTP v2).\n * - **Cognito User Pool** — REST v1 (`Type === 'COGNITO_USER_POOLS'`)\n * extracts the JWT from `Authorization: Bearer <token>`.\n * - **JWT** — HTTP v2 (`AuthorizerType === 'JWT'`). The `JwtConfiguration`\n * names the `Issuer` and `Audience` for verification.\n *\n * Out of scope (hard-errored at discovery):\n * - REST v1 Custom authorizers with non-Lambda backing.\n * - mTLS / VPC Lambda authorizers (the latter is not a separate kind —\n * its Lambda is just a VPC-config Lambda; we warn at startup but do\n * not block).\n *\n * Supported with signature-verification-only semantics:\n * - REST v1 IAM authorizers (`AuthorizationType === 'AWS_IAM'`, #447):\n * the local server verifies SigV4 signatures against the dev's local\n * credentials but does NOT evaluate IAM resource / action / condition\n * policies. See `src/local/sigv4-verify.ts`.\n */\n\nexport interface LambdaTokenAuthorizer {\n kind: 'lambda-token';\n /** `AWS::ApiGateway::Authorizer` logical ID. */\n logicalId: string;\n /** Lambda logical ID resolved from `AuthorizerUri`. */\n lambdaLogicalId: string;\n /**\n * The single header whose value is the token. AWS docs name this\n * `IdentitySource`, e.g. `method.request.header.Authorization`. cdk-local\n * stores the bare lowercased header name (`authorization`).\n */\n tokenHeader: string;\n /** TTL in seconds; 0 disables caching. Default 300, max 3600 (REST v1 cap). */\n resultTtlSeconds: number;\n /** Diagnostic. */\n declaredAt: string;\n}\n\nexport interface LambdaRequestAuthorizer {\n kind: 'lambda-request';\n logicalId: string;\n lambdaLogicalId: string;\n /** Identity-source selectors normalized to a stable shape. */\n identitySources: ReadonlyArray<IdentitySourceSelector>;\n /** TTL in seconds; HTTP v2 stores this on the route, REST v1 on the authorizer. */\n resultTtlSeconds: number;\n /** Discriminator: 'rest-v1' uses 401 on missing identity, 'http-v2' falls through. */\n apiVersion: 'v1' | 'v2';\n declaredAt: string;\n}\n\n/**\n * A single Cognito User Pool referenced by a REST v1\n * `AWS::ApiGateway::Authorizer.ProviderARNs[]` entry. cdk-local extracts the\n * `region` + `userPoolId` segments out of the ARN so the JWKS URL can be\n * built without an AWS API call.\n */\nexport interface CognitoPoolRef {\n /** Original ARN as declared in `ProviderARNs[]`. */\n userPoolArn: string;\n /** Region parsed from the user pool ARN — used to build the JWKS URL. */\n region: string;\n /** Pool id parsed from the ARN. */\n userPoolId: string;\n}\n\nexport interface CognitoUserPoolAuthorizer {\n kind: 'cognito';\n logicalId: string;\n /**\n * Every Cognito user pool declared on the authorizer. CFn allows\n * `ProviderARNs[]` to carry 1+ entries — multi-pool federation is a\n * supported pattern for multi-tenant SaaS where each tenant has its\n * own user pool but shares one API Gateway authorizer. At request\n * time, the JWT's `iss` claim is matched against this list and the\n * matching pool's JWKS is used for signature verification.\n *\n * The list is non-empty (discovery rejects an empty `ProviderARNs[]`).\n */\n pools: ReadonlyArray<CognitoPoolRef>;\n /**\n * Backwards-compatible alias for `pools[0].userPoolArn`. Pre-PR the\n * authorizer carried a single pool; this field is retained so any\n * downstream consumer reading `userPoolArn` keeps working. New code\n * should read {@link pools} instead.\n */\n userPoolArn: string;\n /** Backwards-compatible alias for `pools[0].region`. */\n region: string;\n /** Backwards-compatible alias for `pools[0].userPoolId`. */\n userPoolId: string;\n // NOTE: there is intentionally no `audience` field on REST v1 Cognito\n // authorizers. The audience that JWT verification would check (`aud`\n // for ID tokens, `client_id` for access tokens) is the User Pool *App\n // Client ID*. CDK / CFn's `AWS::ApiGateway::Authorizer` only carries\n // the User Pool ARN(s) via `ProviderARNs`, not the client id, so\n // there's no template-time data for cdk-local to surface here.\n // `verifyCognitoJwt` therefore passes `expectedAudience: undefined`\n // and falls back to issuer / signature / expiry checks only — matches\n // the deployed REST v1 behavior. HTTP v2 JWT authorizers DO carry an\n // explicit audience allowlist via `JwtConfiguration.Audience`; see\n // {@link JwtAuthorizer.audience}.\n declaredAt: string;\n}\n\nexport interface JwtAuthorizer {\n kind: 'jwt';\n logicalId: string;\n /** OIDC issuer URL (HTTP v2's `JwtConfiguration.Issuer`). */\n issuer: string;\n /**\n * Allowed audiences. JWT's `aud` claim must match one of these (or\n * `client_id` for non-Cognito tokens that omit `aud`).\n */\n audience: ReadonlyArray<string>;\n /**\n * For Cognito-issued JWTs the issuer URL embeds the user pool id and\n * we can derive the JWKS URL automatically. Other issuers also expose\n * `<issuer>/.well-known/jwks.json` so we use the same fetch path.\n */\n region?: string;\n userPoolId?: string;\n declaredAt: string;\n}\n\n/**\n * REST v1 `AuthorizationType: 'AWS_IAM'` (closes #447).\n *\n * Unlike the other authorizer kinds, AWS_IAM has NO `AWS::ApiGateway::Authorizer`\n * resource in the template — the method's `AuthorizationType` is the only\n * signal. The local server verifies the request's SigV4 signature against\n * the dev's local credentials (see `src/local/sigv4-verify.ts`). IAM\n * policy evaluation (resource / action / condition) is intentionally not\n * emulated — that requires the deployed IAM data plane.\n */\nexport interface IamAuthorizer {\n kind: 'iam';\n /** Synthetic logical id — there is no real `AWS::ApiGateway::Authorizer` resource. */\n logicalId: 'AWS_IAM';\n declaredAt: string;\n /**\n * Set only for Function URLs (`AWS::Lambda::Url`) fronted by a CloudFront\n * Origin Access Control that signs origin requests. In production\n * CloudFront re-signs the origin request with its own identity, so the\n * END client never signs as the IAM principal and no local client\n * signature can be verified. The HTTP server keeps SigV4 verification in\n * warn-and-pass mode for these routes even under `--strict-sigv4`. Never\n * set for REST v1 AWS_IAM (clients there genuinely sign with their own\n * credentials, so `--strict-sigv4` applies to them). See\n * {@link isFunctionUrlOacFronted}.\n */\n oacFronted?: boolean;\n}\n\nexport type AuthorizerInfo =\n | LambdaTokenAuthorizer\n | LambdaRequestAuthorizer\n | CognitoUserPoolAuthorizer\n | JwtAuthorizer\n | IamAuthorizer;\n\nexport type IdentitySourceSelector =\n | { kind: 'header'; name: string }\n | { kind: 'query'; name: string }\n | { kind: 'context'; name: string }\n | { kind: 'stage-variable'; name: string };\n\n/**\n * Resolve an `AWS::ApiGateway::Authorizer` referenced by a REST v1 method\n * to its {@link AuthorizerInfo} record. Returns `undefined` when the\n * authorizer is intentionally unsupported (the caller treats this as\n * \"no authorizer\", which is wrong but produces a warn line — see\n * `route-discovery.ts`).\n */\nexport function resolveRestV1Authorizer(\n authorizerLogicalId: string,\n template: CloudFormationTemplate,\n stackName: string,\n declaredAt: string\n): AuthorizerInfo {\n const authResource = template.Resources?.[authorizerLogicalId];\n if (!authResource || authResource.Type !== 'AWS::ApiGateway::Authorizer') {\n throw new RouteDiscoveryError(\n `${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGateway::Authorizer in stack '${stackName}'.`\n );\n }\n const props = authResource.Properties ?? {};\n const type = props['Type'];\n\n if (type === 'TOKEN') {\n const lambdaLogicalId = resolveLambdaArn(\n props['AuthorizerUri'],\n `${stackName}/${authorizerLogicalId}.AuthorizerUri`\n );\n const identitySource =\n typeof props['IdentitySource'] === 'string'\n ? props['IdentitySource']\n : 'method.request.header.Authorization';\n const tokenHeader = parseRestV1HeaderSelector(identitySource, stackName, authorizerLogicalId);\n const ttl = parseTtl(props['AuthorizerResultTtlInSeconds'], 300, 3600);\n return {\n kind: 'lambda-token',\n logicalId: authorizerLogicalId,\n lambdaLogicalId,\n tokenHeader,\n resultTtlSeconds: ttl,\n declaredAt,\n };\n }\n\n if (type === 'REQUEST') {\n const lambdaLogicalId = resolveLambdaArn(\n props['AuthorizerUri'],\n `${stackName}/${authorizerLogicalId}.AuthorizerUri`\n );\n const identitySources = parseRestV1IdentitySources(\n typeof props['IdentitySource'] === 'string' ? props['IdentitySource'] : ''\n );\n const ttl = parseTtl(props['AuthorizerResultTtlInSeconds'], 300, 3600);\n return {\n kind: 'lambda-request',\n logicalId: authorizerLogicalId,\n lambdaLogicalId,\n identitySources,\n resultTtlSeconds: ttl,\n apiVersion: 'v1',\n declaredAt,\n };\n }\n\n if (type === 'COGNITO_USER_POOLS') {\n const arns = props['ProviderARNs'];\n if (!Array.isArray(arns) || arns.length === 0) {\n throw new RouteDiscoveryError(\n `${stackName}/${authorizerLogicalId}: COGNITO_USER_POOLS authorizer is missing ProviderARNs.`\n );\n }\n // Walk every ProviderARNs[] entry — CFn allows 1+ for multi-pool\n // federation (multi-tenant SaaS with one pool per tenant). A single\n // malformed entry aborts the whole authorizer at discovery time so\n // the user sees the exact offending index, not a mid-request failure\n // for one tenant only.\n const pools: CognitoPoolRef[] = arns.map((entry, idx) => {\n const arn = pickStringFromArn(\n entry,\n `${stackName}/${authorizerLogicalId}.ProviderARNs[${idx}]`\n );\n const parsed = parseCognitoUserPoolArn(\n arn,\n `${stackName}/${authorizerLogicalId}.ProviderARNs[${idx}]`\n );\n return { userPoolArn: arn, region: parsed.region, userPoolId: parsed.userPoolId };\n });\n const first = pools[0]!;\n return {\n kind: 'cognito',\n logicalId: authorizerLogicalId,\n pools,\n userPoolArn: first.userPoolArn,\n region: first.region,\n userPoolId: first.userPoolId,\n declaredAt,\n };\n }\n\n // Unknown Type — surface a structured error that the route-discovery\n // layer wraps with offending route info. Note: AWS_IAM is detected at\n // the Method level (`AuthorizationType: 'AWS_IAM'`), not here — CDK\n // does not emit a companion `AWS::ApiGateway::Authorizer` resource\n // for IAM. mTLS lives on the `AWS::ApiGateway::DomainName` /\n // `AWS::ApiGatewayV2::DomainName` resource via `MutualTlsAuthentication`,\n // also not via Authorizer; cdk-local supports it via the `--mtls-truststore`\n // / `--mtls-cert` / `--mtls-key` CLI flags on `cdkl start-api`,\n // and the TLS handshake itself enforces the client-cert trust check,\n // orthogonal to (and composable with) TOKEN / REQUEST / COGNITO_USER_POOLS\n // authorizers.\n throw new RouteDiscoveryError(\n `${stackName}/${authorizerLogicalId}: AWS::ApiGateway::Authorizer.Type '${String(type)}' is not supported by ${getEmbedConfig().cliName} start-api (only TOKEN / REQUEST / COGNITO_USER_POOLS are accepted at the Authorizer resource).`\n );\n}\n\n/**\n * Resolve an `AWS::ApiGatewayV2::Authorizer`. HTTP v2 has only `REQUEST`\n * and `JWT`; everything else is unsupported.\n */\nexport function resolveHttpApiAuthorizer(\n authorizerLogicalId: string,\n routeAuthorizationScopes: readonly string[] | undefined,\n template: CloudFormationTemplate,\n stackName: string,\n declaredAt: string\n): AuthorizerInfo {\n const authResource = template.Resources?.[authorizerLogicalId];\n if (!authResource || authResource.Type !== 'AWS::ApiGatewayV2::Authorizer') {\n throw new RouteDiscoveryError(\n `${declaredAt}: AuthorizerId '${authorizerLogicalId}' does not point at an AWS::ApiGatewayV2::Authorizer in stack '${stackName}'.`\n );\n }\n const props = authResource.Properties ?? {};\n const authType = props['AuthorizerType'];\n\n if (authType === 'REQUEST') {\n const lambdaLogicalId = resolveLambdaArn(\n props['AuthorizerUri'],\n `${stackName}/${authorizerLogicalId}.AuthorizerUri`\n );\n const identitySources = parseHttpV2IdentitySources(props['IdentitySource']);\n const ttl = parseTtl(props['AuthorizerResultTtlInSeconds'], 0, 3600);\n return {\n kind: 'lambda-request',\n logicalId: authorizerLogicalId,\n lambdaLogicalId,\n identitySources,\n resultTtlSeconds: ttl,\n apiVersion: 'v2',\n declaredAt,\n };\n }\n\n if (authType === 'JWT') {\n const jwt = props['JwtConfiguration'];\n if (!jwt || typeof jwt !== 'object') {\n throw new RouteDiscoveryError(\n `${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.JwtConfiguration is required for AuthorizerType=JWT.`\n );\n }\n const obj = jwt as Record<string, unknown>;\n const issuer = obj['Issuer'];\n if (typeof issuer !== 'string' || issuer.length === 0) {\n throw new RouteDiscoveryError(\n `${stackName}/${authorizerLogicalId}: JwtConfiguration.Issuer must be a string.`\n );\n }\n const audienceRaw = obj['Audience'];\n const audience = Array.isArray(audienceRaw)\n ? audienceRaw.filter((s): s is string => typeof s === 'string')\n : [];\n\n // Cognito-issued JWTs encode the user pool id in the issuer URL; we\n // detect that shape so the JWKS fetcher can use the canonical\n // `cognito-idp.<region>.amazonaws.com/<userPoolId>/.well-known/jwks.json`\n // URL even when the user-supplied issuer omits the trailing slash.\n const cognito = parseCognitoIssuer(issuer);\n void routeAuthorizationScopes; // scopes are not enforced in v1; accepted for parity.\n return {\n kind: 'jwt',\n logicalId: authorizerLogicalId,\n issuer,\n audience,\n ...(cognito && { region: cognito.region, userPoolId: cognito.userPoolId }),\n declaredAt,\n };\n }\n\n throw new RouteDiscoveryError(\n `${stackName}/${authorizerLogicalId}: AWS::ApiGatewayV2::Authorizer.AuthorizerType '${String(authType)}' is not supported by ${getEmbedConfig().cliName} start-api (only REQUEST / JWT).`\n );\n}\n\n/**\n * Thrown by {@link resolveLambdaArn} when the authorizer's\n * `AuthorizerUri` intrinsic does not resolve to a same-template Lambda\n * (cross-stack reference, imported Lambda, hand-rolled `Fn::Sub` outside\n * the invoke-ARN wrapper).\n *\n * Caught by {@link attachAuthorizers} and converted into a per-route\n * `unsupported` flag — symmetric with how `route-discovery.ts` handles\n * an unresolvable `IntegrationUri`. The route appears in the route\n * table as `[501 Not Implemented]` and returns HTTP 501 + the\n * `reason` at request time. The alternative (\"attach no authorizer,\n * leave route normal\") would be **unsafe** — it would let a request\n * hit a user-protected route without any auth check just because the\n * authorizer Lambda lives in another stack.\n *\n * Private to this module: `attachAuthorizers` is the only legitimate\n * consumer.\n */\nclass AuthorizerLambdaUnresolvableError extends RouteDiscoveryError {\n // Extends RouteDiscoveryError so existing tests that catch the\n // generic `RouteDiscoveryError` (e.g. direct calls to\n // `resolveHttpApiAuthorizer` that bypass `attachAuthorizers`) keep\n // working unchanged. `attachAuthorizers` matches on the more\n // specific subclass first so the deferred-501 path takes priority\n // over the generic catch.\n readonly reason: string;\n constructor(reason: string) {\n super(reason);\n this.reason = reason;\n this.name = 'AuthorizerLambdaUnresolvableError';\n // The parent's constructor calls setPrototypeOf back to\n // RouteDiscoveryError.prototype (a well-known transpile-target\n // workaround for `extends Error`); re-apply ours so\n // `instanceof AuthorizerLambdaUnresolvableError` works in\n // `attachAuthorizers`'s catch.\n Object.setPrototypeOf(this, AuthorizerLambdaUnresolvableError.prototype);\n }\n}\n\n/**\n * Resolve a Lambda ARN intrinsic to its logical ID. Delegates to the\n * shared `resolveLambdaArnIntrinsic` in `intrinsic-lambda-arn.ts`\n * (extracted in issue #286 Gaps 3 / 4); accepts `Ref` /\n * `Fn::GetAtt: [..., 'Arn']` / the REST v1 invoke-ARN `Fn::Join` wrapper\n * (now also used by CDK 2.x's `HttpLambdaAuthorizer` for HTTP API v2 —\n * verified via real `cdk synth` 2026-05-12) / the `Fn::Sub` invoke-ARN\n * wrapper (both 1-arg and 2-arg forms).\n *\n * On an unresolvable intrinsic throws {@link AuthorizerLambdaUnresolvableError}\n * (caught by `attachAuthorizers` and converted into a per-route\n * deferred-501) instead of the generic `RouteDiscoveryError`, so\n * `cdkl start-api` can boot against an app with a cross-stack\n * authorizer Lambda — symmetric with the route-level `IntegrationUri`\n * unresolvable case (issue #431).\n */\nfunction resolveLambdaArn(value: unknown, location: string): string {\n const outcome = resolveLambdaArnShared(value);\n if (outcome.kind === 'resolved') return outcome.logicalId;\n throw new AuthorizerLambdaUnresolvableError(\n `${location}: ${outcome.detail} (got ${shortJson(value)}). Only { Ref }, { Fn::GetAtt: [..., 'Arn'] }, the REST v1 invoke-ARN Fn::Join wrapper, and the Fn::Sub invoke-ARN wrapper are supported.`\n );\n}\n\n/**\n * REST v1 IdentitySource for TOKEN authorizers must be exactly one\n * `method.request.header.<HeaderName>` reference. Returns the bare\n * lowercased header name.\n */\nfunction parseRestV1HeaderSelector(\n identitySource: string,\n stackName: string,\n authorizerLogicalId: string\n): string {\n const m = /^method\\.request\\.header\\.([A-Za-z0-9-_]+)$/.exec(identitySource.trim());\n if (!m) {\n throw new RouteDiscoveryError(\n `${stackName}/${authorizerLogicalId}: TOKEN authorizer IdentitySource '${identitySource}' must be 'method.request.header.<HeaderName>'.`\n );\n }\n return m[1]!.toLowerCase();\n}\n\n/**\n * REST v1 IdentitySource for REQUEST authorizers is a comma-separated\n * list of selectors. Examples:\n * - `method.request.header.X-Api-Key`\n * - `method.request.querystring.token`\n * - `context.identity.sourceIp` (rare)\n * - `stageVariables.foo` (rare)\n *\n * Whitespace around commas is tolerated. Empty input returns `[]`.\n */\nfunction parseRestV1IdentitySources(raw: string): IdentitySourceSelector[] {\n const out: IdentitySourceSelector[] = [];\n for (const tokenRaw of raw.split(',')) {\n const token = tokenRaw.trim();\n if (token.length === 0) continue;\n const headerMatch = /^method\\.request\\.header\\.([A-Za-z0-9-_]+)$/.exec(token);\n if (headerMatch) {\n out.push({ kind: 'header', name: headerMatch[1]!.toLowerCase() });\n continue;\n }\n const queryMatch = /^method\\.request\\.querystring\\.([A-Za-z0-9-_]+)$/.exec(token);\n if (queryMatch) {\n out.push({ kind: 'query', name: queryMatch[1]! });\n continue;\n }\n const contextMatch = /^context\\.([A-Za-z0-9._-]+)$/.exec(token);\n if (contextMatch) {\n out.push({ kind: 'context', name: contextMatch[1]! });\n continue;\n }\n const stageMatch = /^stageVariables\\.([A-Za-z0-9._-]+)$/.exec(token);\n if (stageMatch) {\n out.push({ kind: 'stage-variable', name: stageMatch[1]! });\n continue;\n }\n // Unknown form — keep it as a header selector defensively (matches\n // upstream's tolerant parsing); the cache key still hashes it.\n out.push({ kind: 'header', name: token.toLowerCase() });\n }\n return out;\n}\n\n/**\n * HTTP v2 IdentitySource is an array of `$request.header.X` /\n * `$request.querystring.X` selectors (CDK emits a list).\n */\nfunction parseHttpV2IdentitySources(raw: unknown): IdentitySourceSelector[] {\n if (!Array.isArray(raw)) return [];\n const out: IdentitySourceSelector[] = [];\n for (const entry of raw) {\n if (typeof entry !== 'string') continue;\n const headerMatch = /^\\$request\\.header\\.([A-Za-z0-9-_]+)$/.exec(entry);\n if (headerMatch) {\n out.push({ kind: 'header', name: headerMatch[1]!.toLowerCase() });\n continue;\n }\n const queryMatch = /^\\$request\\.querystring\\.([A-Za-z0-9-_]+)$/.exec(entry);\n if (queryMatch) {\n out.push({ kind: 'query', name: queryMatch[1]! });\n continue;\n }\n out.push({ kind: 'header', name: entry.toLowerCase() });\n }\n return out;\n}\n\n/**\n * Parse a Cognito User Pool ARN\n * `arn:aws:cognito-idp:<region>:<account>:userpool/<region>_<id>`\n * into its region and pool id.\n */\nfunction parseCognitoUserPoolArn(\n arn: string,\n location: string\n): { region: string; userPoolId: string } {\n const m = /^arn:aws[a-z0-9-]*:cognito-idp:([a-z0-9-]+):[0-9]+:userpool\\/(.+)$/.exec(arn);\n if (!m) {\n throw new RouteDiscoveryError(\n `${location}: malformed Cognito User Pool ARN '${arn}'. Expected 'arn:aws:cognito-idp:<region>:<account>:userpool/<id>'.`\n );\n }\n return { region: m[1]!, userPoolId: m[2]! };\n}\n\n/**\n * Detect Cognito-issued JWT issuer URLs and pluck the region + pool id\n * out for the JWKS fetcher. Issuer URLs look like\n * `https://cognito-idp.<region>.amazonaws.com/<userPoolId>` — non-Cognito\n * URLs return undefined and the JWKS fetcher uses the OIDC discovery\n * convention (`<issuer>/.well-known/jwks.json`) instead.\n */\nfunction parseCognitoIssuer(issuer: string): { region: string; userPoolId: string } | undefined {\n const m = /^https:\\/\\/cognito-idp\\.([a-z0-9-]+)\\.amazonaws\\.com\\/([^/]+)\\/?$/.exec(issuer);\n if (!m) return undefined;\n return { region: m[1]!, userPoolId: m[2]! };\n}\n\n/**\n * Pull a string out of a literal / `Fn::GetAtt` entry under `ProviderARNs`.\n *\n * CDK's `apigateway.CognitoUserPoolsAuthorizer` emits a `Fn::GetAtt:\n * [<UserPool>, 'Arn']` reference, which is the canonical shape any user\n * who writes `new CognitoUserPoolsAuthorizer(this, 'auth', { cognitoUserPools: [pool] })`\n * ends up with (#470). Without `--from-state` we cannot resolve the\n * deployed pool ARN, so we synthesize an obviously-unreachable placeholder\n * pointing at a non-existent pool id — the JWKS fetch will fail and\n * cognito-jwt.ts's pass-through fallback (PR #234) admits every JWT\n * without signature verification. The warn log names the affected\n * authorizer + the recommended explicit `providerArns` workaround so\n * developers who DO want real verification know how to switch over.\n *\n * The `location` argument carries the full\n * `<stack>/<authorizer>.ProviderARNs[<idx>]` path so the warn / error\n * names the offending entry exactly.\n */\nfunction pickStringFromArn(value: unknown, location: string): string {\n if (typeof value === 'string') return value;\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const obj = value as Record<string, unknown>;\n if ('Fn::GetAtt' in obj) {\n const arg = obj['Fn::GetAtt'];\n if (\n Array.isArray(arg) &&\n arg.length === 2 &&\n typeof arg[0] === 'string' &&\n arg[1] === 'Arn'\n ) {\n const logicalId = arg[0];\n getLogger().warn(\n `${location}: uses Fn::GetAtt against logical ID '${logicalId}'. ` +\n `${getEmbedConfig().cliName} start-api cannot resolve the deployed user pool ARN — synthesizing ` +\n `an unreachable placeholder so JWKS pass-through admits every token. ` +\n `For real signature verification, set 'providerArns: [pool.userPoolArn]' explicitly on the CDK construct.`\n );\n // The placeholder region is informational — the JWKS URL it\n // produces (`https://cognito-idp.us-east-1.amazonaws.com/<pool>/.well-known/jwks.json`)\n // is supposed to 404 so the pass-through path is the only outcome.\n // The pool id is namespaced with the logical id so the warn line is\n // easy to grep back to the offending authorizer if multiple coexist.\n return `arn:aws:cognito-idp:us-east-1:000000000000:userpool/us-east-1_${getEmbedConfig().binaryName}placeholder${logicalId}`;\n }\n }\n }\n throw new RouteDiscoveryError(`${location}: must be a literal string (got ${shortJson(value)}).`);\n}\n\n/**\n * Parse and clamp a TTL value to `[0, max]` with a default fallback.\n * The TTL field is optional — undefined / non-number / negative → default.\n */\nfunction parseTtl(raw: unknown, fallback: number, max: number): number {\n if (typeof raw !== 'number' || !Number.isFinite(raw) || raw < 0) return fallback;\n return Math.min(Math.trunc(raw), max);\n}\n\n/**\n * Stable cache key for a `(authorizer, identity)` pair. Used by\n * {@link AuthorizerCache}; declared here so the resolver and the cache\n * agree on the hash semantics.\n *\n * For TOKEN authorizers the identity-hash is the token itself. For\n * REQUEST authorizers we concatenate every identity-source selector's\n * resolved value with `\\u0000` as the separator — control chars cannot\n * appear in HTTP header values so collision-by-substring is impossible.\n */\nexport function buildIdentityHash(parts: ReadonlyArray<string | undefined>): string {\n return parts.map((p) => p ?? '').join('\\u0000');\n}\n\n/**\n * Walk every stack and route, attaching an {@link AuthorizerInfo} to each\n * route whose `AuthorizationType` references one. Routes without an\n * authorizer keep `authorizer: undefined`. Routes that reference an\n * intentionally-unsupported authorizer kind (IAM, etc.) hard-fail via\n * {@link RouteDiscoveryError}.\n *\n * **Pure-functional** — does not mutate `routes`. Returns a parallel\n * array of `{route, authorizer}` records that the http-server consumes.\n */\nexport interface RouteWithAuth {\n route: import('./route-discovery.js').DiscoveredRoute;\n authorizer?: AuthorizerInfo;\n}\n\nexport function attachAuthorizers(\n stacks: readonly StackInfo[],\n routes: readonly import('./route-discovery.js').DiscoveredRoute[]\n): RouteWithAuth[] {\n // Build a per-stack lookup so we don't re-scan stacks per route.\n const stackByRoute = new Map<string, StackInfo>();\n for (const stack of stacks) {\n const prefix = `${stack.stackName}/`;\n for (const route of routes) {\n if (route.declaredAt.startsWith(prefix)) stackByRoute.set(route.declaredAt, stack);\n }\n }\n\n const out: RouteWithAuth[] = [];\n const errors: string[] = [];\n\n for (const route of routes) {\n // Skip deferred-error / mockCors routes — neither reaches the\n // authorizer pass at request time (the http-server short-circuits\n // before it). Service-integration routes DO get the authorizer\n // attached (closes #502): the http-server runs the authorizer\n // pass before dispatching to the SDK adapter, so an auth-protected\n // SQS / EventBridge / etc. route rejects unauthenticated requests\n // the same way Lambda routes do.\n if (route.unsupported || route.mockCors) {\n out.push({ route });\n continue;\n }\n const stack = stackByRoute.get(route.declaredAt);\n if (!stack) {\n // This shouldn't happen — every route's declaredAt has a stack\n // prefix — but defensive: pass the route through with no authorizer.\n out.push({ route });\n continue;\n }\n try {\n const authorizer = detectAuthorizer(route, stack);\n out.push({ route, ...(authorizer && { authorizer }) });\n } catch (err) {\n // Authorizer Lambda Arn unresolvable (cross-stack / imported /\n // unsupported intrinsic shape): flip the route to `unsupported`\n // with the resolver's reason instead of aborting boot. Mirrors\n // route-discovery.ts's treatment of an unresolvable\n // `IntegrationUri` so authorizer-protected routes degrade to\n // HTTP 501 + reason at request time rather than blocking every\n // other route on the API (issue #431). The alternative — leave\n // the route normal with no authorizer attached — would silently\n // expose a user-protected route, so we err on the safe side.\n if (err instanceof AuthorizerLambdaUnresolvableError) {\n out.push({\n route: {\n ...route,\n unsupported: {\n reason: `${route.declaredAt}: authorizer Lambda Arn unresolvable — ${err.reason}`,\n },\n },\n });\n continue;\n }\n errors.push(err instanceof Error ? err.message : String(err));\n }\n }\n\n if (errors.length > 0) {\n throw new RouteDiscoveryError(\n `${getEmbedConfig().cliName} start-api: ${errors.length} authorizer error(s):\\n` +\n errors.map((e) => ` - ${e}`).join('\\n')\n );\n }\n return out;\n}\n\n/**\n * Detect the authorizer (if any) attached to a discovered route.\n * Walks the original CFn resource for the route in `stack.template`.\n */\nfunction detectAuthorizer(\n route: import('./route-discovery.js').DiscoveredRoute,\n stack: StackInfo\n): AuthorizerInfo | undefined {\n // declaredAt looks like `<stackName>/<logicalId>` — peel the logical id\n // back out so we can find the originating CFn resource.\n const slash = route.declaredAt.indexOf('/');\n if (slash < 0) return undefined;\n const logicalId = route.declaredAt.slice(slash + 1);\n const resource = stack.template.Resources?.[logicalId];\n if (!resource) return undefined;\n\n if (resource.Type === 'AWS::ApiGateway::Method') {\n return detectRestV1Authorizer(resource, logicalId, stack);\n }\n if (resource.Type === 'AWS::ApiGatewayV2::Route') {\n return detectHttpApiAuthorizer(resource, logicalId, stack);\n }\n if (resource.Type === 'AWS::Lambda::Url') {\n return detectFunctionUrlAuthorizer(resource, logicalId, stack);\n }\n return undefined;\n}\n\n/**\n * Function URL (`AWS::Lambda::Url`) authorizer detection (issue #621).\n *\n * `AuthType: 'AWS_IAM'` uses the same SigV4 mechanism REST v1 ships\n * (PR #447), so we route through the same `IamAuthorizer` descriptor and\n * let the HTTP server's existing `if (authorizer.kind === 'iam')`\n * request-time branch run `verifySigV4`. Like REST v1 AWS_IAM, no IAM\n * policy emulation — signature verification only.\n *\n * `AuthType: 'NONE'` (and any non-AWS_IAM AuthType that\n * `route-discovery.ts` already flipped to `unsupported`) returns\n * `undefined` so the route runs without an authorizer pass.\n */\nfunction detectFunctionUrlAuthorizer(\n urlResource: TemplateResource,\n urlLogicalId: string,\n stack: StackInfo\n): AuthorizerInfo | undefined {\n const props = urlResource.Properties ?? {};\n const authType = props['AuthType'];\n if (authType !== 'AWS_IAM') return undefined;\n return {\n kind: 'iam',\n logicalId: 'AWS_IAM',\n declaredAt: `${stack.stackName}/${urlLogicalId}`,\n // OAC-fronted Function URLs are signed by CloudFront in production; the\n // local server never sees that signature, so relax verification rather\n // than denying every request. See `isFunctionUrlOacFronted`.\n ...(isFunctionUrlOacFronted(stack.template, urlLogicalId) && { oacFronted: true }),\n };\n}\n\nfunction detectRestV1Authorizer(\n methodResource: TemplateResource,\n methodLogicalId: string,\n stack: StackInfo\n): AuthorizerInfo | undefined {\n const props = methodResource.Properties ?? {};\n const authType = props['AuthorizationType'];\n if (authType === undefined || authType === 'NONE') return undefined;\n\n // AWS_IAM has no companion `AWS::ApiGateway::Authorizer` resource —\n // the AuthorizationType alone is the signal. SigV4 signatures are\n // verified at request time by `src/local/sigv4-verify.ts` (#447).\n if (authType === 'AWS_IAM') {\n return {\n kind: 'iam',\n logicalId: 'AWS_IAM',\n declaredAt: `${stack.stackName}/${methodLogicalId}`,\n };\n }\n\n const authorizerId = props['AuthorizerId'];\n const refLogicalId = pickRefLogicalId(authorizerId);\n\n // CUSTOM / COGNITO_USER_POOLS / etc. all need an AuthorizerId Ref.\n if (!refLogicalId) {\n throw new RouteDiscoveryError(\n `${stack.stackName}/${methodLogicalId}: AuthorizationType='${stringifyValue(authType)}' but AuthorizerId is missing or not a {Ref:...}.`\n );\n }\n\n return resolveRestV1Authorizer(\n refLogicalId,\n stack.template,\n stack.stackName,\n `${stack.stackName}/${methodLogicalId}`\n );\n}\n\nfunction detectHttpApiAuthorizer(\n routeResource: TemplateResource,\n routeLogicalId: string,\n stack: StackInfo\n): AuthorizerInfo | undefined {\n const props = routeResource.Properties ?? {};\n const authType = props['AuthorizationType'];\n if (authType === undefined || authType === 'NONE') return undefined;\n\n const authorizerId = props['AuthorizerId'];\n const refLogicalId = pickRefLogicalId(authorizerId);\n if (!refLogicalId) {\n throw new RouteDiscoveryError(\n `${stack.stackName}/${routeLogicalId}: AuthorizationType='${stringifyValue(authType)}' but AuthorizerId is missing or not a {Ref:...}.`\n );\n }\n const scopesRaw = props['AuthorizationScopes'];\n const scopes = Array.isArray(scopesRaw)\n ? scopesRaw.filter((s): s is string => typeof s === 'string')\n : undefined;\n\n return resolveHttpApiAuthorizer(\n refLogicalId,\n scopes,\n stack.template,\n stack.stackName,\n `${stack.stackName}/${routeLogicalId}`\n );\n}\n\nfunction shortJson(value: unknown): string {\n try {\n const s = JSON.stringify(value);\n return s.length > 200 ? `${s.slice(0, 200)}…` : s;\n } catch {\n return stringifyValue(value);\n }\n}\n","import { createPublicKey, createVerify } from 'node:crypto';\nimport { getLogger } from '../utils/logger.js';\nimport type { CachedAuthorizerResult } from './authorizer-cache.js';\nimport type { CognitoUserPoolAuthorizer, JwtAuthorizer } from './authorizer-resolver.js';\nimport { buildIdentityHash } from './authorizer-resolver.js';\n\n/**\n * Cognito User Pool / JWT authorizer support for `cdkl start-api`\n * (PR 8b).\n *\n * cdk-local verifies JWTs locally against the user pool's published JWKS so\n * the developer can exercise authorizer-protected routes with real-ish\n * tokens (e.g. ones minted by `aws cognito-idp admin-initiate-auth`).\n *\n * **JWKS-fetch failure handling** (locked design decision): when the\n * JWKS endpoint is unreachable at startup, we warn and fall back to a\n * pass-through mode where every JWT is accepted as if valid. Surprising\n * deny is worse than warn+allow for a dev tool. The warn line names the\n * unreachable URL so users can investigate (proxy, network, missing\n * pool) and is repeated on first request to the affected authorizer.\n *\n * Spec references:\n * - Cognito JWT structure:\n * https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html\n * - JWKS:\n * https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html\n */\n\ninterface JwksKey {\n /** Key id; matches the `kid` claim in the JWT header. */\n kid: string;\n /** RSA modulus, base64url-encoded. */\n n: string;\n /** RSA exponent, base64url-encoded (typically `AQAB`). */\n e: string;\n /** Algorithm — always `RS256` for Cognito. */\n alg?: string;\n /** Key type — `RSA`. */\n kty: string;\n /** Key use — `sig` for signing keys. */\n use?: string;\n}\n\ninterface JwksCacheEntry {\n /** All keys keyed by `kid` for O(1) lookup. */\n byKid: Map<string, JwksKey>;\n /** When this entry expires (unix ms). 1hr from fetch by default. */\n expiresAt: number;\n /** True when the fetch failed and we're in pass-through mode. */\n passThrough: boolean;\n}\n\n/**\n * Cache of JWKS responses, keyed by the JWKS URL. cdk-local refreshes the\n * cache lazily on miss; entries live for 1hr by default (Cognito rotates\n * keys infrequently, so this is conservative).\n */\nexport interface JwksCache {\n fetchAndCache(jwksUrl: string): Promise<JwksCacheEntry>;\n /** Get the cached entry without refreshing — returns undefined on miss. */\n peek(jwksUrl: string): JwksCacheEntry | undefined;\n clear(): void;\n}\n\nconst DEFAULT_JWKS_TTL_MS = 60 * 60 * 1000;\n/**\n * Failure-mode TTL for JWKS-unreachable entries. Pre-fix the failure\n * entry inherited the 1hr success TTL, so a single transient blip\n * locked pass-through mode for a full hour. 60s is short enough that\n * the next minute's request triggers a real refetch while still\n * suppressing the per-request fetch storm a 0s TTL would cause.\n */\nconst FAILURE_JWKS_TTL_MS = 60 * 1000;\n\nexport function createJwksCache(\n opts: {\n fetchImpl?: (\n url: string\n ) => Promise<{ ok: boolean; status: number; text: () => Promise<string> }>;\n now?: () => number;\n ttlMs?: number;\n /** Failure-mode TTL override (defaults to {@link FAILURE_JWKS_TTL_MS}). */\n failureTtlMs?: number;\n } = {}\n): JwksCache {\n const fetchImpl = opts.fetchImpl ?? (async (url) => globalThis.fetch(url));\n const now = opts.now ?? ((): number => Date.now());\n const ttlMs = opts.ttlMs ?? DEFAULT_JWKS_TTL_MS;\n const failureTtlMs = opts.failureTtlMs ?? FAILURE_JWKS_TTL_MS;\n const map = new Map<string, JwksCacheEntry>();\n\n return {\n async fetchAndCache(jwksUrl) {\n const cached = map.get(jwksUrl);\n if (cached && cached.expiresAt > now()) return cached;\n const logger = getLogger().child('cognito-jwt');\n try {\n const response = await fetchImpl(jwksUrl);\n if (!response.ok) {\n throw new Error(`JWKS fetch returned HTTP ${response.status}`);\n }\n const body = await response.text();\n const parsed = JSON.parse(body) as { keys?: unknown };\n const keys = Array.isArray(parsed.keys) ? parsed.keys : [];\n const byKid = new Map<string, JwksKey>();\n for (const k of keys) {\n if (!k || typeof k !== 'object' || Array.isArray(k)) continue;\n const obj = k as Record<string, unknown>;\n if (\n typeof obj['kid'] === 'string' &&\n typeof obj['n'] === 'string' &&\n typeof obj['e'] === 'string' &&\n typeof obj['kty'] === 'string'\n ) {\n byKid.set(obj['kid'], {\n kid: obj['kid'],\n n: obj['n'],\n e: obj['e'],\n kty: obj['kty'],\n ...(typeof obj['alg'] === 'string' && { alg: obj['alg'] }),\n ...(typeof obj['use'] === 'string' && { use: obj['use'] }),\n });\n }\n }\n const entry: JwksCacheEntry = {\n byKid,\n expiresAt: now() + ttlMs,\n passThrough: false,\n };\n map.set(jwksUrl, entry);\n return entry;\n } catch (err) {\n logger.warn(\n `JWKS unreachable at ${jwksUrl}: ${err instanceof Error ? err.message : String(err)}. ` +\n `JWT validation will allow all tokens — local dev fallback. Configure network access to the JWKS URL ` +\n `to enable real signature verification.`\n );\n // Short-TTL failure entry so a transient blip doesn't lock\n // pass-through mode for a full hour. The next minute's request\n // re-attempts the fetch.\n const entry: JwksCacheEntry = {\n byKid: new Map(),\n expiresAt: now() + failureTtlMs,\n passThrough: true,\n };\n map.set(jwksUrl, entry);\n return entry;\n }\n },\n peek(jwksUrl) {\n return map.get(jwksUrl);\n },\n clear() {\n map.clear();\n },\n };\n}\n\n/**\n * Build the JWKS URL for a Cognito User Pool.\n *\n * Format: `https://cognito-idp.<region>.amazonaws.com/<userPoolId>/.well-known/jwks.json`\n */\nexport function buildCognitoJwksUrl(region: string, userPoolId: string): string {\n return `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`;\n}\n\n/**\n * Build the JWKS URL for a generic OIDC issuer (HTTP v2 JWT authorizers).\n * Trailing slash is normalized.\n */\nexport function buildJwksUrlFromIssuer(issuer: string): string {\n const stripped = issuer.replace(/\\/+$/, '');\n return `${stripped}/.well-known/jwks.json`;\n}\n\n/**\n * Build the expected `iss` claim URL for a Cognito user pool. Matches the\n * issuer Cognito embeds in every minted JWT.\n */\nfunction buildCognitoIssuer(region: string, userPoolId: string): string {\n return `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`;\n}\n\n/**\n * Verify a Bearer JWT against the Cognito user pool(s) referenced by the\n * authorizer. With a multi-pool authorizer (`ProviderARNs[]` of length\n * 1+) the request-time pool selection is driven by the JWT's unverified\n * `iss` claim — only the matching pool's JWKS is used for signature\n * verification, so a token issued by pool A cannot be verified against\n * pool B's keys. Issuer mismatch against EVERY configured pool rejects\n * with 401.\n *\n * Returns `{ allow: false }` on:\n * - missing / malformed Authorization header (caller surfaces 401);\n * - signature verification failure;\n * - expired token (`exp` in the past);\n * - issuer mismatch (token's `iss` doesn't match any configured pool);\n * - audience mismatch (token's `aud` not in the configured allowlist).\n *\n * Returns `{ allow: true, principalId, context }` on:\n * - successful verification against the matching pool;\n * - JWKS-unreachable pass-through mode for the matching pool (with a\n * warn line on first hit; per-pool TTL handled by the cache).\n *\n * Backward compat: a single-element `ProviderARNs[]` (the historical\n * single-pool case) behaves identically to pre-PR — `pools[0]` is the\n * only candidate and the `iss` check matches it.\n */\nexport async function verifyCognitoJwt(\n authorizer: CognitoUserPoolAuthorizer,\n authorizationHeader: string | undefined,\n jwksCache: JwksCache,\n opts: { now?: () => number; warned?: Set<string> } = {}\n): Promise<CachedAuthorizerResult & { identityHash: string | undefined; ttlSeconds: number }> {\n const now = opts.now ?? ((): number => Date.now());\n const token = extractBearer(authorizationHeader);\n if (!token) {\n return { allow: false, identityHash: undefined, ttlSeconds: 0 };\n }\n // `pools` is the canonical list; older callers that build the\n // authorizer literal (tests) may still pass legacy single-pool shape\n // — fall back to the legacy fields in that case.\n const pools =\n authorizer.pools && authorizer.pools.length > 0\n ? authorizer.pools\n : [\n {\n userPoolArn: authorizer.userPoolArn,\n region: authorizer.region,\n userPoolId: authorizer.userPoolId,\n },\n ];\n // Parse the token first so we can read its (unverified) `iss` claim\n // and pick the matching pool. parseJwt() failure is mapped to deny so\n // a garbage token never reaches the JWKS fetcher in the real-pool\n // (non pass-through) path — but the pass-through pool path needs to\n // accept malformed tokens, which the verify helper handles below.\n const parsed = parseJwt(token);\n const identityHash = buildIdentityHash([token]);\n\n // Pick the pool whose expected issuer URL matches the token's `iss`.\n // Strip trailing slashes defensively (a real Cognito token issuer\n // never has one, but be tolerant).\n let selectedPool = pools[0]!;\n let issMatched = false;\n if (parsed && typeof parsed.payload['iss'] === 'string') {\n const tokenIss = parsed.payload['iss'].replace(/\\/+$/, '');\n for (const pool of pools) {\n if (buildCognitoIssuer(pool.region, pool.userPoolId) === tokenIss) {\n selectedPool = pool;\n issMatched = true;\n break;\n }\n }\n // Issuer mismatch against EVERY configured pool: reject without\n // touching JWKS. Multi-pool federation must NOT accept a token\n // whose issuer was never registered.\n if (!issMatched) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n } else if (pools.length > 1) {\n // Multi-pool federation safety: when there is more than one\n // configured pool AND the token did not parse (or has no string\n // `iss`), we have no safe way to route the request to a pool. The\n // pass-through fallback would arbitrarily pick pools[0] and, if\n // pool[0] happens to be in JWKS-pass-through mode, accept the\n // garbage token as `principalId: 'unknown'` — bypassing the\n // configured-issuer guard. Reject unconditionally instead.\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n // Single-pool case OR no-iss with one pool: fall through to\n // verifyAndShape against pools[0]. parseJwt-failure deny is enforced\n // there; if the pool is in pass-through mode, the malformed token\n // gets the \"unknown\" principal allow path (preserves PR 8b's design\n // intent that JWKS-unreachable accepts any Bearer token for that\n // single configured pool).\n const jwksUrl = buildCognitoJwksUrl(selectedPool.region, selectedPool.userPoolId);\n const expectedIssuer = buildCognitoIssuer(selectedPool.region, selectedPool.userPoolId);\n return verifyAndShape(\n token,\n jwksUrl,\n expectedIssuer,\n undefined,\n undefined,\n undefined,\n jwksCache,\n opts.warned,\n now\n );\n}\n\n/**\n * Verify a Bearer JWT against an HTTP v2 JWT authorizer's `JwtConfiguration`.\n */\nexport async function verifyJwtAuthorizer(\n authorizer: JwtAuthorizer,\n authorizationHeader: string | undefined,\n jwksCache: JwksCache,\n opts: { now?: () => number; warned?: Set<string> } = {}\n): Promise<CachedAuthorizerResult & { identityHash: string | undefined; ttlSeconds: number }> {\n const now = opts.now ?? ((): number => Date.now());\n const token = extractBearer(authorizationHeader);\n if (!token) {\n return { allow: false, identityHash: undefined, ttlSeconds: 0 };\n }\n // Cognito-issued JWTs let us hit the canonical JWKS URL directly. Other\n // issuers use OIDC discovery convention (`<issuer>/.well-known/jwks.json`).\n const jwksUrl =\n authorizer.region && authorizer.userPoolId\n ? buildCognitoJwksUrl(authorizer.region, authorizer.userPoolId)\n : buildJwksUrlFromIssuer(authorizer.issuer);\n return verifyAndShape(\n token,\n jwksUrl,\n authorizer.issuer.replace(/\\/+$/, ''),\n authorizer.audience,\n undefined,\n undefined,\n jwksCache,\n opts.warned,\n now\n );\n}\n\n/**\n * Inbound custom-JWT authorizer fronted by an OIDC discovery URL — the\n * shape Bedrock AgentCore Runtime declares\n * (`AuthorizerConfiguration.CustomJWTAuthorizer`).\n */\nexport interface DiscoveryJwtAuthorizer {\n /** OIDC discovery document URL (`.well-known/openid-configuration`). */\n discoveryUrl: string;\n /** Allowed `aud` values. */\n allowedAudience?: readonly string[];\n /** Allowed `client_id` values. */\n allowedClients?: readonly string[];\n /**\n * OAuth scopes the token's `scope` claim must include (space-separated\n * scope strings; the token must carry every entry in this allowlist).\n */\n allowedScopes?: readonly string[];\n /** Per-claim equality / membership rules the token must satisfy. */\n customClaims?: readonly JwtCustomClaim[];\n}\n\n/**\n * A single `CustomJWTAuthorizer.CustomClaims[]` rule the verifier evaluates\n * against the token's claims. Mirrors {@link AgentCoreCustomClaim} in the\n * resolver — kept structurally identical so the resolver -> verifier hand-off\n * is a direct field copy.\n */\nexport interface JwtCustomClaim {\n name: string;\n valueType: 'STRING' | 'STRING_ARRAY';\n operator: 'EQUALS' | 'CONTAINS' | 'CONTAINS_ANY';\n value: string | string[];\n}\n\n/**\n * Verify a Bearer JWT against an OIDC-discovery-URL authorizer (Bedrock\n * AgentCore Runtime's `customJwtAuthorizer`).\n *\n * Fetches the discovery document to learn the `issuer` + `jwks_uri`, then\n * delegates to {@link verifyAndShape} for the RS256 signature + `iss` +\n * `exp` + audience checks. The audience allowlist is\n * `allowedAudience ∪ allowedClients`, matched against the token's `aud`\n * (ID tokens) or `client_id` (access tokens).\n *\n * Failure modes mirror the JWKS-unreachable behavior: an absent / malformed\n * Bearer token is denied, but an unreachable / malformed discovery document\n * falls back to pass-through (accept + warn) so offline local dev still\n * works — the same trade-off `cdkl start-api` makes for unreachable JWKS.\n */\nexport async function verifyJwtViaDiscovery(\n authorizer: DiscoveryJwtAuthorizer,\n authorizationHeader: string | undefined,\n jwksCache: JwksCache,\n opts: {\n now?: () => number;\n warned?: Set<string>;\n fetchImpl?: (\n url: string\n ) => Promise<{ ok: boolean; status: number; text: () => Promise<string> }>;\n } = {}\n): Promise<CachedAuthorizerResult & { identityHash: string | undefined; ttlSeconds: number }> {\n const now = opts.now ?? ((): number => Date.now());\n const token = extractBearer(authorizationHeader);\n if (!token) {\n return { allow: false, identityHash: undefined, ttlSeconds: 0 };\n }\n const fetchImpl =\n opts.fetchImpl ?? (async (url): ReturnType<typeof globalThis.fetch> => globalThis.fetch(url));\n\n let issuer: string;\n let jwksUri: string;\n try {\n const response = await fetchImpl(authorizer.discoveryUrl);\n if (!response.ok) {\n throw new Error(`discovery fetch returned HTTP ${response.status}`);\n }\n const doc = JSON.parse(await response.text()) as { issuer?: unknown; jwks_uri?: unknown };\n if (typeof doc.issuer !== 'string' || typeof doc.jwks_uri !== 'string') {\n throw new Error('discovery document missing issuer / jwks_uri');\n }\n issuer = doc.issuer;\n jwksUri = doc.jwks_uri;\n } catch (err) {\n // Discovery unreachable / malformed → pass-through accept (offline dev),\n // mirroring the JWKS-unreachable fallback in `createJwksCache`.\n if (opts.warned && !opts.warned.has(authorizer.discoveryUrl)) {\n opts.warned.add(authorizer.discoveryUrl);\n getLogger()\n .child('cognito-jwt')\n .warn(\n `OIDC discovery unreachable at ${authorizer.discoveryUrl}: ${\n err instanceof Error ? err.message : String(err)\n }. Token accepted without verification — local dev fallback.`\n );\n }\n const identityHash = buildIdentityHash([token]);\n const parsed = parseJwt(token);\n if (parsed) return shapeAllowResult(parsed, identityHash, now);\n return { allow: true, principalId: 'unknown', context: {}, identityHash, ttlSeconds: 0 };\n }\n\n const allowlist = [...(authorizer.allowedAudience ?? []), ...(authorizer.allowedClients ?? [])];\n return verifyAndShape(\n token,\n jwksUri,\n issuer.replace(/\\/+$/, ''),\n allowlist.length > 0 ? allowlist : undefined,\n authorizer.allowedScopes,\n authorizer.customClaims,\n jwksCache,\n opts.warned,\n now\n );\n}\n\nasync function verifyAndShape(\n token: string,\n jwksUrl: string,\n expectedIssuer: string,\n expectedAudience: ReadonlyArray<string> | undefined,\n requiredScopes: ReadonlyArray<string> | undefined,\n customClaims: ReadonlyArray<JwtCustomClaim> | undefined,\n jwksCache: JwksCache,\n warned: Set<string> | undefined,\n now: () => number\n): Promise<CachedAuthorizerResult & { identityHash: string | undefined; ttlSeconds: number }> {\n const identityHash = buildIdentityHash([token]);\n\n // Fetch JWKS first so the pass-through mode (JWKS unreachable) can\n // accept every Bearer token — including malformed / non-JWT garbage —\n // without needing to first parse the token. Pre-fix the parseJwt\n // check above the JWKS fetch denied malformed tokens even in\n // pass-through mode, contradicting the design intent (\"every JWT\n // accepted as if valid\" → \"every Bearer token accepted\").\n const jwks = await jwksCache.fetchAndCache(jwksUrl);\n\n if (jwks.passThrough) {\n if (warned && !warned.has(jwksUrl)) {\n warned.add(jwksUrl);\n getLogger()\n .child('cognito-jwt')\n .warn(\n `JWKS pass-through mode for ${jwksUrl}: token accepted without signature verification.`\n );\n }\n // Best-effort parse: a real JWT lets us still surface claims to the\n // handler. A malformed token gets a synthetic `unknown` principal\n // and an empty claims map. Either way the request is allowed.\n const parsed = parseJwt(token);\n if (parsed) {\n return shapeAllowResult(parsed, identityHash, now);\n }\n return {\n allow: true,\n principalId: 'unknown',\n context: {},\n identityHash,\n ttlSeconds: 0,\n };\n }\n\n const parsed = parseJwt(token);\n if (!parsed) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n\n const kid = parsed.header['kid'];\n if (typeof kid !== 'string') {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n const key = jwks.byKid.get(kid);\n if (!key) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n\n if (!verifyRs256(token, key)) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n\n // Validate `exp`.\n const claims = parsed.payload;\n if (typeof claims['exp'] !== 'number' || claims['exp'] * 1000 <= now()) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n // Validate `iss` (best-effort — strip any trailing slash on either side).\n if (typeof claims['iss'] !== 'string' || claims['iss'].replace(/\\/+$/, '') !== expectedIssuer) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n // Validate `aud` / `client_id`. Cognito access tokens use `client_id`,\n // ID tokens use `aud`. We accept whichever matches the allowlist.\n if (expectedAudience && expectedAudience.length > 0) {\n const aud = claims['aud'];\n const clientId = claims['client_id'];\n const audValues = Array.isArray(aud) ? aud : aud !== undefined ? [aud] : [];\n const matches =\n audValues.some((v) => typeof v === 'string' && expectedAudience.includes(v)) ||\n (typeof clientId === 'string' && expectedAudience.includes(clientId));\n if (!matches) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n }\n\n // Validate `scope`. AgentCore's `AllowedScopes` is a required-scope\n // allowlist: the token's `scope` claim (OAuth space-separated) must include\n // every entry. A token with no `scope` claim fails when scopes are required.\n if (requiredScopes && requiredScopes.length > 0) {\n if (!verifyRequiredScopes(claims['scope'], requiredScopes)) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n }\n\n // Validate custom claims. Every rule must hold against the token's\n // matching claim value; a token missing a referenced claim fails.\n if (customClaims && customClaims.length > 0) {\n for (const rule of customClaims) {\n if (!verifyCustomClaim(claims[rule.name], rule)) {\n return { allow: false, identityHash, ttlSeconds: 0 };\n }\n }\n }\n\n return shapeAllowResult(parsed, identityHash, now);\n}\n\n/**\n * The OAuth `scope` claim is a space-separated string. The token is allowed\n * iff every required scope is present (allowlist as REQUIRED, not OR).\n */\nfunction verifyRequiredScopes(scopeClaim: unknown, requiredScopes: ReadonlyArray<string>): boolean {\n const tokenScopes =\n typeof scopeClaim === 'string'\n ? scopeClaim.split(/\\s+/).filter((s) => s.length > 0)\n : Array.isArray(scopeClaim)\n ? scopeClaim.filter((s): s is string => typeof s === 'string')\n : [];\n return requiredScopes.every((s) => tokenScopes.includes(s));\n}\n\n/**\n * Verify a single `CustomJWTAuthorizer.CustomClaims` rule against the token's\n * claim value:\n *\n * - `STRING` + `EQUALS` — claim is a string equal to `value`.\n * - `STRING_ARRAY` + `CONTAINS` — claim is an array containing `value`.\n * - `STRING_ARRAY` + `CONTAINS_ANY` — claim is an array sharing at least one\n * entry with `value` (an array of allowed strings).\n *\n * A missing or wrong-typed claim fails the rule.\n */\nfunction verifyCustomClaim(claimValue: unknown, rule: JwtCustomClaim): boolean {\n if (rule.valueType === 'STRING') {\n if (rule.operator !== 'EQUALS' || typeof rule.value !== 'string') return false;\n return typeof claimValue === 'string' && claimValue === rule.value;\n }\n // STRING_ARRAY\n if (!Array.isArray(claimValue)) return false;\n const tokenValues = claimValue.filter((v): v is string => typeof v === 'string');\n if (rule.operator === 'CONTAINS') {\n if (typeof rule.value !== 'string') return false;\n return tokenValues.includes(rule.value);\n }\n if (rule.operator === 'CONTAINS_ANY') {\n if (!Array.isArray(rule.value)) return false;\n return rule.value.some((v) => tokenValues.includes(v));\n }\n return false;\n}\n\n/**\n * Construct the Allow result for a verified JWT. The handler-side context\n * is the parsed claim map; principalId mirrors Cognito's deployed\n * behavior (the `sub` claim, falling back to `username` then `cognito:username`).\n */\nfunction shapeAllowResult(\n parsed: ParsedJwt,\n identityHash: string,\n now: () => number\n): CachedAuthorizerResult & { identityHash: string; ttlSeconds: number } {\n const claims = parsed.payload;\n const principalId =\n pickStringClaim(claims, 'sub') ??\n pickStringClaim(claims, 'cognito:username') ??\n pickStringClaim(claims, 'username') ??\n 'unknown';\n // Cap TTL at min(remaining-exp, 300s). The local server shouldn't\n // outlive a real JWT; cdk-local caches modestly to avoid spamming the\n // signature verifier on every request.\n const expMs = typeof claims['exp'] === 'number' ? claims['exp'] * 1000 : 0;\n const remainingSeconds = Math.max(0, Math.floor((expMs - now()) / 1000));\n const ttlSeconds = Math.min(300, remainingSeconds);\n return {\n allow: true,\n principalId,\n context: claims,\n identityHash,\n ttlSeconds,\n };\n}\n\nfunction pickStringClaim(claims: Record<string, unknown>, key: string): string | undefined {\n const v = claims[key];\n return typeof v === 'string' ? v : undefined;\n}\n\n/**\n * Parse `Authorization: Bearer <token>` into the bare token. Whitespace\n * around `Bearer` is tolerated; case is matched leniently. The token\n * itself is constrained to the JWT character class `[A-Za-z0-9._-]+`\n * (base64url alphabet plus the JWT `.` separator), so embedded\n * whitespace / quotes / other garbage in the header rejects rather than\n * being passed along to the JWT parser as a token containing spaces.\n * Returns `undefined` when the header is missing, the scheme is wrong,\n * or the token doesn't look JWT-shaped.\n */\nfunction extractBearer(header: string | undefined): string | undefined {\n if (!header) return undefined;\n const m = /^\\s*Bearer\\s+([A-Za-z0-9._-]+)\\s*$/i.exec(header);\n if (!m) return undefined;\n return m[1]!;\n}\n\ninterface ParsedJwt {\n header: Record<string, unknown>;\n payload: Record<string, unknown>;\n signingInput: string; // `<headerB64>.<payloadB64>`\n signatureB64: string;\n}\n\n/**\n * Parse a JWT into `(header, payload, signingInput, signatureB64)`.\n * Returns undefined on malformed input — the caller maps that to deny.\n */\nfunction parseJwt(token: string): ParsedJwt | undefined {\n const parts = token.split('.');\n if (parts.length !== 3) return undefined;\n try {\n const headerJson = base64UrlDecodeToString(parts[0]!);\n const payloadJson = base64UrlDecodeToString(parts[1]!);\n const header = JSON.parse(headerJson) as Record<string, unknown>;\n const payload = JSON.parse(payloadJson) as Record<string, unknown>;\n return {\n header,\n payload,\n signingInput: `${parts[0]}.${parts[1]}`,\n signatureB64: parts[2]!,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Verify an RS256 JWT signature against an RSA public key derived from\n * the JWKS entry. Uses Node's `crypto.createPublicKey({key, format: 'jwk'})`\n * — Node 16+ understands the JWK format directly.\n */\nfunction verifyRs256(token: string, key: JwksKey): boolean {\n const parts = token.split('.');\n if (parts.length !== 3) return false;\n const signingInput = `${parts[0]}.${parts[1]}`;\n const signature = base64UrlDecodeToBuffer(parts[2]!);\n try {\n // Node 16+ accepts the JWK shape via `format: 'jwk'`. Cast the\n // input through `unknown` so we don't need the DOM-side `JsonWebKey`\n // type at compile time.\n const publicKey = createPublicKey({\n key: { kty: key.kty, n: key.n, e: key.e },\n format: 'jwk',\n } as unknown as Parameters<typeof createPublicKey>[0]);\n const verifier = createVerify('RSA-SHA256');\n verifier.update(signingInput);\n verifier.end();\n return verifier.verify(publicKey, signature);\n } catch {\n return false;\n }\n}\n\nfunction base64UrlDecodeToString(input: string): string {\n return base64UrlDecodeToBuffer(input).toString('utf-8');\n}\n\nfunction base64UrlDecodeToBuffer(input: string): Buffer {\n // Add padding back: base64url strips `=` padding.\n const padded = input.replace(/-/g, '+').replace(/_/g, '/');\n const padding = padded.length % 4 === 0 ? '' : '='.repeat(4 - (padded.length % 4));\n return Buffer.from(padded + padding, 'base64');\n}\n","/**\n * Minimal, linear-time glob matcher for `cdk.json` `watch.include` /\n * `watch.exclude` patterns (used by `cdkl start-api --watch` source-tree\n * watching).\n *\n * chokidar v4 dropped built-in glob support, so the watcher matches the\n * cdk.json watch globs here. Supported syntax (the subset CDK's own\n * `cdk watch` config uses):\n *\n * - `**` (a whole path segment) — matches zero or more path segments,\n * i.e. crosses `/`.\n * - `*` — any run of characters except `/` (within one segment).\n * - `?` — a single character except `/`.\n * - a pattern with no `/` matches its basename at any depth\n * (`node_modules` is treated as `**` + `/node_modules`), mirroring\n * gitignore / cdk-watch behavior.\n *\n * A pattern also matches the CONTENTS of a directory it names\n * (`node_modules` matches `node_modules/x/y`) so a directory exclude\n * prunes the whole subtree. Brace / extglob / character-class syntax is\n * NOT supported — cdk.json watch configs don't use it.\n *\n * Matching is implemented with two classic two-pointer wildcard passes\n * (one over path segments for `**`, one within a segment for `*` / `?`)\n * rather than a translated `RegExp`. This is deliberate: a `RegExp`\n * built from many `*` (each `[^/]*`) backtracks catastrophically on a\n * non-matching input, and these matchers run on EVERY watched file\n * event with user-supplied patterns. The two-pointer form is O(n*m) and\n * cannot blow up.\n *\n * Paths are matched in POSIX form (forward slashes); the matcher\n * normalizes Windows separators before testing.\n */\n\nexport type GlobMatcher = (relPath: string) => boolean;\n\n/**\n * Normalize a glob and split it into path-segment tokens. Returns\n * `null` for an empty pattern (callers skip it). A slash-free pattern is\n * prefixed with a `**` token so it matches its basename at any depth.\n */\nfunction compilePattern(glob: string): string[] | null {\n const g = glob.replace(/\\\\/g, '/').replace(/^\\.\\//, '').replace(/\\/+$/, '');\n if (g === '') return null;\n if (!g.includes('/')) return ['**', g];\n return g.split('/');\n}\n\n/**\n * Linear wildcard match for ONE path segment (no `/`). `*` matches any\n * run of characters, `?` matches exactly one. Classic two-pointer\n * algorithm with single backtrack pointer — O(len(pat) * len(str)),\n * never exponential.\n */\nfunction matchSegment(pat: string, str: string): boolean {\n let s = 0;\n let p = 0;\n let starP = -1;\n let starS = 0;\n while (s < str.length) {\n const pc = pat[p];\n if (p < pat.length && (pc === str[s] || pc === '?')) {\n s++;\n p++;\n } else if (pc === '*') {\n starP = p;\n starS = s;\n p++;\n } else if (starP !== -1) {\n p = starP + 1;\n starS++;\n s = starS;\n } else {\n return false;\n }\n }\n while (pat[p] === '*') p++;\n return p === pat.length;\n}\n\n/**\n * Match a compiled pattern's segment tokens against a path's segments. A\n * `**` token matches zero or more path segments; every other token\n * matches exactly one segment via {@link matchSegment}. The pattern\n * matches when it consumes a PREFIX of the path (the contents rule), so\n * a directory pattern also matches everything beneath it. Single\n * backtrack pointer over `**` — O(patSegs * pathSegs), never\n * exponential.\n */\nfunction matchSegments(pat: readonly string[], path: readonly string[]): boolean {\n let pi = 0;\n let si = 0;\n let starPi = -1;\n let starSi = 0;\n while (si < path.length) {\n if (pi === pat.length) return true; // pattern consumed -> contents match\n if (pat[pi] === '**') {\n starPi = pi;\n starSi = si;\n pi++;\n } else if (matchSegment(pat[pi] as string, path[si] as string)) {\n pi++;\n si++;\n } else if (starPi !== -1) {\n pi = starPi + 1;\n starSi++;\n si = starSi;\n } else {\n return false;\n }\n }\n if (pi === pat.length) return true;\n while (pi < pat.length && pat[pi] === '**') pi++;\n return pi === pat.length;\n}\n\n/**\n * Compile a list of globs into a single matcher. The returned predicate\n * is `true` when the (relative, POSIX-normalized) path matches ANY\n * pattern. An empty or all-invalid pattern list yields a matcher that\n * never matches.\n */\nexport function createGlobMatcher(patterns: readonly string[]): GlobMatcher {\n const compiled: string[][] = [];\n for (const p of patterns) {\n const segs = compilePattern(p);\n if (segs) compiled.push(segs);\n }\n return (relPath: string): boolean => {\n const pathSegs = relPath.replace(/\\\\/g, '/').split('/');\n return compiled.some((pat) => matchSegments(pat, pathSegs));\n };\n}\n","import { randomUUID } from 'node:crypto';\n\n/**\n * AWS API Gateway WebSocket event-payload builders.\n *\n * Spec: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html\n *\n * Three event types — CONNECT / MESSAGE / DISCONNECT — each carry a\n * shared {@link WebSocketRequestContext} plus per-event fields.\n *\n * Fields cdk-local populates locally vs mocks (matches design Q1 in\n * `docs/design/462-websocket-api.md`):\n *\n * | Field | Source |\n * |--------------------------------------|-----------------------------------------|\n * | `connectionId` | UUID v4 generated at `$connect` |\n * | `requestId` / `extendedRequestId` | Generated UUID per event |\n * | `messageId` (MESSAGE only) | Generated UUID per event |\n * | `requestTime` / `requestTimeEpoch` | `Date.now()` at build time |\n * | `connectedAt` | Captured at `$connect` |\n * | `stage` | Resolved Stage Name; `'local'` default |\n * | `apiId` | `'local'` (mock) |\n * | `domainName` | `'localhost'` (mock) |\n * | `identity.sourceIp` | `req.socket.remoteAddress` (real) |\n * | `identity.userAgent` | Upgrade `User-Agent` header (real) |\n * | `headers`/`queryStringParameters` | Parsed from upgrade `req` (real) |\n * | `authorizer` | `null` in v1 (deferred) |\n *\n * Per-event `eventType` / `routeKey` / `messageDirection` are fixed by\n * the lifecycle stage, NOT by the route's `routeKey` field — `routeKey`\n * carries which user-declared route fired (\"$connect\" / \"$disconnect\" /\n * \"$default\" / custom).\n */\n\nconst MOCK_ACCOUNT_ID = '123456789012';\nconst MOCK_DOMAIN_NAME = 'localhost';\nconst MOCK_API_ID = 'local';\n\n/**\n * Request snapshot from the WebSocket upgrade handshake. The handshake is\n * a plain HTTP GET with `Upgrade: websocket`; cdk-local extracts headers /\n * query string / source IP from the underlying `IncomingMessage` once at\n * `$connect` and reuses them for every subsequent event on the same\n * connection so the Lambda's event-context stays consistent.\n *\n * Headers are passed in their on-wire case; the builders lowercase them\n * per AWS spec.\n */\nexport interface WebSocketHandshakeSnapshot {\n /** Header map: header-name → array of values (multiple of the same name preserved). */\n headers: Record<string, string[]>;\n /** Raw query string (NOT decoded) from the upgrade URL. */\n rawQueryString: string;\n /** Parsed query parameters (decoded). Single-value: last wins. */\n queryStringParameters?: Record<string, string>;\n /** Multi-value query parameters preserved. */\n multiValueQueryStringParameters?: Record<string, string[]>;\n /** Source IP from `req.socket.remoteAddress`. */\n sourceIp?: string;\n /** User-Agent header (already extracted, for `identity.userAgent`). */\n userAgent?: string;\n}\n\n/**\n * Shared shape of the WebSocket event's `requestContext` field. Per-event\n * builders add the discriminator (`eventType`, `routeKey`, `messageId`)\n * plus event-specific fields like `disconnectStatusCode` on top of this.\n */\nexport interface WebSocketRequestContextBase {\n routeKey: string;\n eventType: 'CONNECT' | 'MESSAGE' | 'DISCONNECT';\n connectionId: string;\n extendedRequestId: string;\n requestTime: string;\n requestTimeEpoch: number;\n messageDirection: 'IN';\n stage: string;\n connectedAt: number;\n requestId: string;\n domainName: string;\n apiId: string;\n authorizer: null;\n identity: {\n accountId: string;\n sourceIp: string;\n userAgent: string;\n };\n}\n\n/** Top-level event object passed to the route's Lambda. */\nexport interface WebSocketLambdaEvent {\n /** Header map (lowercase keys, comma-joined when multi-valued). */\n headers?: Record<string, string>;\n /** Multi-value headers (lowercase keys). */\n multiValueHeaders?: Record<string, string[]>;\n /** Query string parameters (single-value, last wins). */\n queryStringParameters?: Record<string, string> | null;\n /** Multi-value query parameters. */\n multiValueQueryStringParameters?: Record<string, string[]> | null;\n /** Request context with routing / identity metadata. */\n requestContext: WebSocketRequestContextBase & Record<string, unknown>;\n /** Always `false` in v1 — frame bodies are passed through as text. */\n isBase64Encoded: boolean;\n /** Frame body for MESSAGE events; empty string on CONNECT / DISCONNECT. */\n body: string;\n}\n\n/**\n * Build a request-context block shared across all three event types.\n * `eventType` / `routeKey` are passed in by the per-event caller; the\n * shared block produces fresh `requestId` / `extendedRequestId` per\n * event (matching AWS-deployed behavior — these are NOT stable across\n * events on the same connection).\n */\nfunction buildRequestContext(\n routeKey: string,\n eventType: 'CONNECT' | 'MESSAGE' | 'DISCONNECT',\n connectionId: string,\n connectedAt: number,\n stage: string,\n snapshot: WebSocketHandshakeSnapshot\n): WebSocketRequestContextBase {\n const now = Date.now();\n return {\n routeKey,\n eventType,\n connectionId,\n extendedRequestId: randomUUID(),\n requestTime: formatRequestTime(now),\n requestTimeEpoch: now,\n messageDirection: 'IN',\n stage,\n connectedAt,\n requestId: randomUUID(),\n domainName: MOCK_DOMAIN_NAME,\n apiId: MOCK_API_ID,\n authorizer: null,\n identity: {\n // Mirror AWS-deployed WebSocket events: `requestContext.identity.accountId`\n // carries the API owner's account id. Local emulation hard-codes the\n // shared mock account so handler code reading this field is non-undefined,\n // matching the deployed surface. The constant is exported via\n // `WEBSOCKET_MOCK_CONSTANTS` so integ tests + handlers can assert against\n // the same value.\n accountId: MOCK_ACCOUNT_ID,\n sourceIp: snapshot.sourceIp ?? '127.0.0.1',\n userAgent: snapshot.userAgent ?? '',\n },\n };\n}\n\n/**\n * Build the `$connect` event. AWS WebSocket APIs fire `$connect` ONCE\n * per client connection. Handler returns `{statusCode: 200}` to allow\n * the connection, anything else (or throws) to deny — cdk-local matches the\n * deployed behavior by checking the response in the caller.\n */\nexport function buildConnectEvent(opts: {\n connectionId: string;\n connectedAt: number;\n stage: string;\n snapshot: WebSocketHandshakeSnapshot;\n}): WebSocketLambdaEvent {\n const headers = normalizeHeaders(opts.snapshot.headers);\n const multiValueHeaders = lowercaseMultiValueHeaders(opts.snapshot.headers);\n return {\n ...(headers !== undefined && { headers }),\n ...(multiValueHeaders !== undefined && { multiValueHeaders }),\n queryStringParameters: opts.snapshot.queryStringParameters ?? null,\n multiValueQueryStringParameters: opts.snapshot.multiValueQueryStringParameters ?? null,\n requestContext: {\n ...buildRequestContext(\n '$connect',\n 'CONNECT',\n opts.connectionId,\n opts.connectedAt,\n opts.stage,\n opts.snapshot\n ),\n },\n isBase64Encoded: false,\n body: '',\n };\n}\n\n/**\n * Build a MESSAGE event. Fires for every frame the client sends. The\n * route the API dispatches to is resolved upstream by the route\n * selection-expression layer; the resolved `routeKey` (`$default` or\n * a custom string) lands on `requestContext.routeKey`.\n */\nexport function buildMessageEvent(opts: {\n connectionId: string;\n connectedAt: number;\n stage: string;\n snapshot: WebSocketHandshakeSnapshot;\n routeKey: string;\n body: string;\n /**\n * Whether the body is base64-encoded. True iff the source WebSocket\n * frame was binary (opcode 0x2); false for text frames (opcode 0x1).\n * Matches AWS-deployed WebSocket API semantics — handlers reading\n * `event.body` decode with `Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8')`.\n * Hardcoding this to `false` (pre-fix) silently corrupted every byte\n * > 0x7F because the UTF-8 decoder rejected / replaced binary bytes.\n */\n isBase64Encoded: boolean;\n}): WebSocketLambdaEvent {\n return {\n requestContext: {\n ...buildRequestContext(\n opts.routeKey,\n 'MESSAGE',\n opts.connectionId,\n opts.connectedAt,\n opts.stage,\n opts.snapshot\n ),\n messageId: randomUUID(),\n },\n isBase64Encoded: opts.isBase64Encoded,\n body: opts.body,\n };\n}\n\n/**\n * Build the `$disconnect` event. Fires when the WebSocket closes from\n * either side (client / server / abnormal). The Lambda's response is\n * ignored (the socket is already gone); AWS still invokes the handler\n * for cleanup / logging side effects.\n *\n * `disconnectStatusCode` / `disconnectReason` are taken from the\n * WebSocket close frame (RFC 6455 §7.1.5 — close codes such as 1000\n * normal / 1001 going-away / 1008 policy-violation).\n */\nexport function buildDisconnectEvent(opts: {\n connectionId: string;\n connectedAt: number;\n stage: string;\n snapshot: WebSocketHandshakeSnapshot;\n disconnectStatusCode?: number;\n disconnectReason?: string;\n}): WebSocketLambdaEvent {\n return {\n requestContext: {\n ...buildRequestContext(\n '$disconnect',\n 'DISCONNECT',\n opts.connectionId,\n opts.connectedAt,\n opts.stage,\n opts.snapshot\n ),\n ...(opts.disconnectStatusCode !== undefined && {\n disconnectStatusCode: opts.disconnectStatusCode,\n }),\n ...(opts.disconnectReason !== undefined && {\n disconnectReason: opts.disconnectReason,\n }),\n },\n isBase64Encoded: false,\n body: '',\n };\n}\n\n/**\n * Format a timestamp in the AWS-canonical `dd/MMM/yyyy:HH:mm:ss +0000`\n * shape that AWS API Gateway emits on `requestContext.requestTime`.\n * Always UTC (matches AWS-deployed behavior, which is region-independent).\n */\nfunction formatRequestTime(epochMs: number): string {\n const d = new Date(epochMs);\n const day = String(d.getUTCDate()).padStart(2, '0');\n const month = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n ][d.getUTCMonth()];\n const year = d.getUTCFullYear();\n const hour = String(d.getUTCHours()).padStart(2, '0');\n const min = String(d.getUTCMinutes()).padStart(2, '0');\n const sec = String(d.getUTCSeconds()).padStart(2, '0');\n return `${day}/${month}/${year}:${hour}:${min}:${sec} +0000`;\n}\n\n/**\n * Lowercase header keys, comma-join duplicates per AWS spec.\n */\nfunction normalizeHeaders(headers: Record<string, string[]>): Record<string, string> | undefined {\n const out: Record<string, string> = {};\n let any = false;\n for (const [name, values] of Object.entries(headers)) {\n if (values.length === 0) continue;\n out[name.toLowerCase()] = values.join(',');\n any = true;\n }\n return any ? out : undefined;\n}\n\n/**\n * Lowercase header keys, preserve multi-value array shape.\n */\nfunction lowercaseMultiValueHeaders(\n headers: Record<string, string[]>\n): Record<string, string[]> | undefined {\n const out: Record<string, string[]> = {};\n let any = false;\n for (const [name, values] of Object.entries(headers)) {\n if (values.length === 0) continue;\n out[name.toLowerCase()] = [...values];\n any = true;\n }\n return any ? out : undefined;\n}\n\n// Re-export the mock identifiers so tests + integ helpers can verify\n// the expected mock values without redefining them.\nexport const WEBSOCKET_MOCK_CONSTANTS = {\n accountId: MOCK_ACCOUNT_ID,\n domainName: MOCK_DOMAIN_NAME,\n apiId: MOCK_API_ID,\n} as const;\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { WebSocket } from 'ws';\n\n/**\n * Local emulation of the AWS `apigatewaymanagementapi` data plane —\n * specifically the `@connections` sub-resource Lambdas use to push\n * messages back to connected clients.\n *\n * Endpoint shape (matches AWS):\n * - `POST /@connections/<connectionId>` — send a message\n * - `GET /@connections/<connectionId>` — get connection metadata (deferred)\n * - `DELETE /@connections/<connectionId>` — force-disconnect (deferred)\n *\n * Handlers route to this module via the env-var override\n * `AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI=http://<host>:<port>` that\n * `cdkl start-api` injects into every WebSocket-API Lambda's\n * container (see `container-pool.ts`). The AWS SDK v3 honors this\n * env var and sends the call to cdk-local's HTTP server instead of the\n * synthetic `*.execute-api.*.amazonaws.com` hostname.\n *\n * Security: cdk-local does NOT verify SigV4 on the inbound request — the\n * dev-loop is not a security boundary (matches the precedent set by\n * `ecs-network.ts`'s metadata-endpoints sidecar). The SDK still signs\n * the request because that's what v3 does unconditionally; cdk-local\n * ignores the signature.\n */\n\n/**\n * Per-API connection registry — one instance per WebSocket API server.\n * Keyed by `connectionId` so the `POST /@connections/<id>` handler can\n * route the payload back to the live WebSocket without scanning every\n * server's registry. Ephemeral by design (no persistence across server\n * restarts) — matches `cdkl start-api`'s overall no-state model.\n */\nexport interface ConnectionRegistryEntry {\n /** UUID v4 generated at `$connect`. Opaque per AWS docs. */\n connectionId: string;\n /** Open `ws.WebSocket`. Used for outbound send + close. */\n socket: WebSocket;\n /** `Date.now()` at `$connect`. Surfaced as `requestContext.connectedAt`. */\n connectedAt: number;\n /** The API the connection belongs to. Used in error messages only. */\n apiLogicalId: string;\n /** Stage name resolved at discovery. Surfaced as `requestContext.stage`. */\n stage: string;\n}\n\n/**\n * `Map<connectionId, ConnectionRegistryEntry>` wrapper with type-safe\n * accessors. Lookups by connectionId stay O(1).\n */\nexport class ConnectionRegistry {\n private readonly entries = new Map<string, ConnectionRegistryEntry>();\n\n register(entry: ConnectionRegistryEntry): void {\n this.entries.set(entry.connectionId, entry);\n }\n\n unregister(connectionId: string): ConnectionRegistryEntry | undefined {\n const entry = this.entries.get(connectionId);\n if (entry) this.entries.delete(connectionId);\n return entry;\n }\n\n get(connectionId: string): ConnectionRegistryEntry | undefined {\n return this.entries.get(connectionId);\n }\n\n size(): number {\n return this.entries.size;\n }\n\n /**\n * Snapshot the live entries (for diagnostics / shutdown drain).\n * Returns a fresh array so the caller can iterate without ownership\n * concerns over the underlying Map.\n */\n list(): ConnectionRegistryEntry[] {\n return Array.from(this.entries.values());\n }\n\n clear(): void {\n this.entries.clear();\n }\n}\n\n/**\n * Match the request URL against the `@connections` endpoint family.\n * Returns the parsed connectionId on match, `null` otherwise.\n *\n * AWS reserves `$` / `@` for control planes so the path prefix\n * `/@connections/` can never collide with user-declared routes.\n *\n * Accepted shapes:\n * - `/@connections/<id>` (AWS-deployed historical form — supported\n * for backward compatibility with handlers that construct URLs\n * manually).\n * - `/<stage>/@connections/<id>` (AWS-docs-canonical form — the\n * deployed apigatewaymanagementapi endpoint URL is\n * `https://<api-id>.execute-api.<region>.amazonaws.com/<stage>`,\n * so SDK-built clients call `POST /<stage>/@connections/<id>`).\n *\n * The optional `<stage>` segment matches AWS's deployed URL exactly —\n * any non-slash sequence preceding `/@connections/`. The stage value\n * itself is intentionally NOT validated against the per-API configured\n * stage name: cdk-local is a local-dev tool, not a security boundary, and\n * an aggressive stage check would just trip on misconfigured handlers\n * without adding any real protection.\n */\nexport function parseConnectionsPath(url: string): {\n connectionId: string;\n} | null {\n // Strip the optional query string (e.g. `?Action=GetConnection`).\n const pathOnly = url.split('?', 1)[0]!;\n const m = /^\\/(?:[^/]+\\/)?@connections\\/([^/]+)\\/?$/.exec(pathOnly);\n if (!m) return null;\n const decoded = safeDecodeURIComponent(m[1]!);\n if (decoded === null) return null;\n return { connectionId: decoded };\n}\n\n/**\n * Build the per-Lambda env-var URL the cdk-local server injects as\n * `AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI`. The URL MUST include the\n * `/<stage>` segment to mirror the AWS-deployed apigatewaymanagementapi\n * endpoint `https://<api-id>.execute-api.<region>.amazonaws.com/<stage>`:\n * SDK clients built from `domainName + stage` produce\n * `POST /<stage>/@connections/<id>`, and the local `parseConnectionsPath`\n * regex above requires the matching prefix. Without `/<stage>` the\n * deployed-shape SDK call hits a 404 against the local parser\n * (BLOCKER B1, #526).\n *\n * Lives next to `parseConnectionsPath` so the producer + consumer of\n * the URL shape stay in lockstep — change one, the other's tests fail.\n * Issue #537 item 7.\n */\nexport function buildMgmtEndpointEnvUrl(host: string, port: number, stage: string): string {\n return `http://${host}:${port}/${stage}`;\n}\n\n/**\n * `decodeURIComponent` throws `URIError` on malformed input\n * (`%`-escape with non-hex tail). We treat that as a not-found rather\n * than a server error — symmetric with AWS-deployed behavior, which\n * returns `GoneException` (HTTP 410) for any connection id it can't\n * look up.\n */\nfunction safeDecodeURIComponent(s: string): string | null {\n try {\n return decodeURIComponent(s);\n } catch {\n return null;\n }\n}\n\n/**\n * Read the full request body into a Buffer. Mirrors `node:http`'s\n * `IncomingMessage` consume pattern — collect chunks, resolve on `end`.\n *\n * The body is what the user's handler passed to\n * `apigatewaymanagementapi.PostToConnection({Data: <bytes>})`. AWS docs\n * say the body is raw bytes (treated as opaque by the API plane); we\n * forward the buffer through to `WebSocket.send` so binary frames work\n * end to end.\n */\nexport function readRequestBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer | string) => {\n if (Buffer.isBuffer(chunk)) chunks.push(chunk);\n else chunks.push(Buffer.from(chunk, 'utf-8'));\n });\n req.on('end', () => {\n resolve(Buffer.concat(chunks));\n });\n req.on('error', reject);\n });\n}\n\n/**\n * Handle a `@connections/<id>` HTTP request. Dispatches by method:\n * - `POST` → push the request body to the matching open WebSocket.\n * - `DELETE` → force-close the WebSocket (1000 normal close).\n * - `GET` → return synthetic metadata for the connection.\n * - anything else → 405.\n *\n * Returns `true` when the request was handled (caller short-circuits),\n * `false` when the URL didn't match (caller continues normal HTTP\n * route dispatch).\n *\n * AWS-correct status codes:\n * - Connection not in registry → `410 Gone` (matches AWS's\n * `GoneException` for closed connections).\n * - Send succeeded → `200 OK` (body empty).\n * - Send failed (socket not OPEN) → `410 Gone` — the connection has\n * started closing on the WebSocket side but the registry entry\n * hasn't been removed yet (the `close` event clean-up is async).\n *\n * NOTE: The body buffer can include arbitrary binary; `ws.send` handles\n * both string and Buffer inputs (the recipient receives the same bytes\n * the sender wrote).\n */\nexport async function handleConnectionsRequest(opts: {\n req: IncomingMessage;\n res: ServerResponse;\n registry: ConnectionRegistry;\n}): Promise<void> {\n const { req, res, registry } = opts;\n const url = req.url ?? '';\n const parsed = parseConnectionsPath(url);\n if (!parsed) {\n writeJson(res, 404, { message: 'Not Found' });\n return;\n }\n\n const { connectionId } = parsed;\n const entry = registry.get(connectionId);\n const method = (req.method ?? '').toUpperCase();\n\n // No-such-connection short-circuit. AWS docs say `GoneException`\n // (HTTP 410) for every method when the connection is unknown.\n if (!entry) {\n writeJson(res, 410, { message: 'GoneException' });\n return;\n }\n\n if (method === 'POST') {\n let body: Buffer;\n try {\n body = await readRequestBody(req);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n writeJson(res, 500, { message: `Failed to read request body: ${reason}` });\n return;\n }\n\n // `ws.send` accepts both Buffer and string; we keep the original\n // bytes so binary payloads round-trip unchanged.\n if (entry.socket.readyState !== entry.socket.OPEN) {\n // Socket is closing / closed but registry hasn't been swept yet.\n // Match AWS's GoneException response.\n writeJson(res, 410, { message: 'GoneException' });\n return;\n }\n try {\n entry.socket.send(body);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n writeJson(res, 500, { message: `Failed to deliver to socket: ${reason}` });\n return;\n }\n // AWS returns 200 with an empty body on successful PostToConnection.\n res.writeHead(200);\n res.end();\n return;\n }\n\n if (method === 'DELETE') {\n // AWS returns 204 on successful DeleteConnection. We close the\n // socket (registry-unregister + $disconnect dispatch fires from\n // the websocket-server module's close listener — single source of\n // truth for cleanup).\n try {\n entry.socket.close(1000, 'DeleteConnection');\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n writeJson(res, 500, { message: `Failed to close socket: ${reason}` });\n return;\n }\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (method === 'GET') {\n // AWS-shape GetConnection response (subset of fields). Real AWS\n // also returns `LastActiveAt` / `Identity`; we synthesize the\n // mandatory subset and omit fields the local server doesn't track.\n writeJson(res, 200, {\n ConnectedAt: new Date(entry.connectedAt).toISOString(),\n Identity: { SourceIp: '127.0.0.1' },\n LastActiveAt: new Date().toISOString(),\n });\n return;\n }\n\n // Method not allowed.\n res.setHeader('Allow', 'POST, GET, DELETE');\n writeJson(res, 405, { message: 'MethodNotAllowedException' });\n}\n\nfunction writeJson(res: ServerResponse, status: number, body: Record<string, unknown>): void {\n const json = JSON.stringify(body);\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(json),\n });\n res.end(json);\n}\n","/**\n * Convert a ws-emitted message buffer into the AWS-canonical event\n * body + `isBase64Encoded` discriminator. Text frames (opcode 0x1) pass\n * through as UTF-8 with `isBase64Encoded: false`; binary frames\n * (opcode 0x2) are base64-encoded with `isBase64Encoded: true`. Matches\n * AWS-deployed WebSocket API event shape exactly — handlers decode via\n * `Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8')`.\n *\n * Closes the data-integrity bug where every byte > 0x7F on a binary\n * frame was silently corrupted by handlers that trusted the previously\n * hardcoded `isBase64Encoded: false` flag and UTF-8-decoded the\n * base64-encoded body.\n *\n * Lives in its own module so the B4 regression test can install a\n * `vi.fn()` spy that intercepts EVERY call — same-module references in\n * `websocket-server.ts` would bypass the export-binding spy. See\n * Issue #537 item 6.\n */\nexport function bufferToBody(\n raw: Buffer | ArrayBuffer | Buffer[],\n isBinary: boolean\n): { body: string; isBase64Encoded: boolean } {\n const buf: Buffer = Array.isArray(raw)\n ? Buffer.concat(raw)\n : Buffer.isBuffer(raw)\n ? raw\n : Buffer.from(raw);\n if (isBinary) {\n return { body: buf.toString('base64'), isBase64Encoded: true };\n }\n return { body: buf.toString('utf-8'), isBase64Encoded: false };\n}\n","import { randomUUID } from 'node:crypto';\nimport type { IncomingMessage, Server as HttpServer, ServerResponse } from 'node:http';\nimport type { Duplex } from 'node:stream';\nimport { WebSocketServer, type WebSocket } from 'ws';\n\nimport { getLogger } from '../utils/logger.js';\nimport type { ContainerPool } from './container-pool.js';\nimport { invokeRie } from './rie-client.js';\nimport {\n buildConnectEvent,\n buildDisconnectEvent,\n buildMessageEvent,\n type WebSocketHandshakeSnapshot,\n type WebSocketLambdaEvent,\n} from './websocket-event.js';\nimport {\n ConnectionRegistry,\n handleConnectionsRequest,\n parseConnectionsPath,\n type ConnectionRegistryEntry,\n} from './websocket-mgmt-api.js';\nimport type { DiscoveredWebSocketApi } from './websocket-route-discovery.js';\nimport { parseSelectionExpressionPath } from './websocket-route-discovery.js';\n// Namespaced import so the B4 regression test (Issue #537 item 6) can\n// spy on `bufferToBody` and intercept every internal call. A direct\n// `import { bufferToBody }` would bind to a local reference the test\n// spy cannot reach.\nimport * as websocketBody from './websocket-body.js';\n\n/**\n * Wire a WebSocket API into a long-lived `node:http`'s `upgrade`\n * pipeline. The same server already serves HTTP API v2 / REST v1 /\n * Function URL routes via the `request` listener; this module adds a\n * sibling `upgrade` listener that handles WebSocket handshakes.\n *\n * Architecture (mirrors design doc §2 / §8):\n * - One {@link WebSocketServer} per cdkl start-api server.\n * - `noServer: true` mode — cdk-local owns the upgrade-event dispatch.\n * - Per-connection lifecycle: handshake -> $connect Lambda ->\n * (allow/deny) -> message loop -> close -> $disconnect Lambda.\n * - Outbound `@connections/<id>` POST from a handler-side AWS SDK\n * call routes to the WebSocket via the shared\n * {@link ConnectionRegistry}.\n *\n * The container pool is the SAME instance the HTTP-side server uses for\n * REST/HTTP API/Function URL routes — WebSocket dispatch is just\n * another consumer; per-Lambda concurrency caps still apply.\n */\n\nconst DEFAULT_INVOKE_TIMEOUT_MS = 60_000;\n\n/**\n * Configuration for one WebSocket API attached to the server.\n *\n * `apiPath` controls the upgrade URL the WebSocket listens on; defaults\n * to `/` so a single-API setup matches the SAM Local UX (`wscat -c\n * ws://host:port`). Multi-API setups use `/<stage>` to disambiguate\n * (mirrors AWS's deployed-URL shape `wss://<id>.execute-api.<region>.amazonaws.com/<stage>`).\n */\nexport interface WebSocketApiConfig {\n /** Discovered WebSocket API metadata. */\n api: DiscoveredWebSocketApi;\n /**\n * URL path that the upgrade request must match. `'/'` by default;\n * `'/<stageName>'` when the server hosts multiple WebSocket APIs.\n * `req.url`'s pathname is matched against this verbatim.\n */\n apiPath: string;\n}\n\n/**\n * Handle returned by {@link attachWebSocketServer}. The CLI's shutdown\n * loop calls `close()` to gracefully tear down every active connection\n * (close frame 1001 going-away) before disposing the container pool.\n */\nexport interface AttachedWebSocketServer {\n /** Connection registry — exposed for the `@connections` HTTP handler. */\n registry: ConnectionRegistry;\n /**\n * Close every live WebSocket with code 1001 (going-away) and stop\n * accepting new upgrades. Safe to call multiple times. Resolves once\n * every socket has emitted its `close` event (which also fires the\n * `$disconnect` Lambda per connection).\n */\n close: () => Promise<void>;\n /**\n * The list of API paths this server now handles. The CLI uses this\n * to print the WebSocket URL on the listening banner.\n */\n apiPaths: readonly string[];\n}\n\n/**\n * Build a per-server connection registry plus a request pre-pass for\n * the `/@connections/<id>` endpoint. Mounted on the SAME node:http\n * server as the HTTP-side dispatcher via a `request`-listener pre-pass\n * (see {@link attachWebSocketServer.handleManagementRequest}).\n */\nexport interface AttachOptions {\n /** Node `http.Server` or `https.Server` to attach to. */\n httpServer: HttpServer;\n /** Apis to wire into the upgrade pipeline. */\n apis: readonly WebSocketApiConfig[];\n /** Container pool sourced from the parent CLI. */\n pool: ContainerPool;\n /**\n * RIE invoke timeout in ms. Defaults to {@link DEFAULT_INVOKE_TIMEOUT_MS};\n * the CLI scales this with the function's `Properties.Timeout` * 2.\n */\n rieTimeoutMs?: number;\n}\n\n/**\n * Attach a WebSocket server to the parent HTTP listener. Returns an\n * {@link AttachedWebSocketServer} the CLI uses for graceful shutdown +\n * to expose the connection registry to the management-API\n * pre-pass.\n *\n * Implementation:\n * - One shared {@link ws.WebSocketServer} in `noServer` mode.\n * - One `upgrade` listener that routes by `req.url`'s pathname; an\n * unrecognized upgrade target is destroyed (RFC 6455 §4.3.2 —\n * server SHOULD respond with HTTP 404 or 426).\n * - Per-connection state held in a {@link ConnectionRegistry} the\n * `@connections` HTTP handler reads to push messages back.\n *\n * Returns synchronously — the underlying ws server is fully bound by\n * the time this function returns.\n */\nexport function attachWebSocketServer(opts: AttachOptions): AttachedWebSocketServer {\n const logger = getLogger().child('start-api/ws');\n const rieTimeoutMs = opts.rieTimeoutMs ?? DEFAULT_INVOKE_TIMEOUT_MS;\n const registry = new ConnectionRegistry();\n const wss = new WebSocketServer({ noServer: true });\n\n // Per-API state lookup. The upgrade listener uses `req.url`'s\n // pathname; the message dispatcher uses the per-connection entry.\n const apisByPath = new Map<string, WebSocketApiConfig>();\n const apiPaths: string[] = [];\n for (const cfg of opts.apis) {\n apisByPath.set(cfg.apiPath, cfg);\n apiPaths.push(cfg.apiPath);\n }\n\n // Per-connection serialized dispatch chain (Issue #527 M1). AWS\n // WebSocket APIs invoke handlers SERIALLY per connection — frame N+1\n // never starts dispatching until frame N's handler returns. Without\n // this chain, three rapid frames from one client race the warm-pool\n // entry and produce out-of-order handler invocations, breaking\n // ordering-dependent handlers (chat history, conversational state).\n // Across-connection dispatch stays parallel via separate map entries.\n const dispatchChainsByConnection = new Map<string, Promise<void>>();\n const enqueueDispatch = (connectionId: string, work: () => Promise<void>): Promise<void> => {\n const prev = dispatchChainsByConnection.get(connectionId) ?? Promise.resolve();\n const next = prev.then(work);\n // Store `next` directly in the Map (NOT `next.finally(...)`): the\n // `=== next` identity check below would never match if we stored\n // the finally-wrapper, and the entry would leak permanently — the\n // pre-fix bug surfaced in PR #539 review. Storing `next` keeps the\n // chain semantics (the next enqueue picks up `next` as its `prev`\n // and the finally-tap runs at the same logical time as `next`\n // resolves) while making the cleanup branch reachable.\n dispatchChainsByConnection.set(connectionId, next);\n void next.finally(() => {\n if (dispatchChainsByConnection.get(connectionId) === next) {\n dispatchChainsByConnection.delete(connectionId);\n }\n });\n return next;\n };\n\n // In-flight $disconnect dispatches tracked so close() can bound the\n // graceful-shutdown drain (Issue #527 M3). A hung $disconnect handler\n // pre-fix would leak its rieTimeoutMs (60s+) past close()'s 5s\n // socket-close timeout — the Node process couldn't exit until SIGKILL\n // or the verify.sh 120s grace.\n const inFlightDisconnects = new Set<Promise<void>>();\n\n const upgradeListener = (req: IncomingMessage, socket: Duplex, head: Buffer): void => {\n const url = req.url ?? '/';\n const pathOnly = url.split('?', 1)[0]!;\n const cfg = apisByPath.get(pathOnly);\n if (!cfg) {\n // Unknown upgrade target. RFC 6455 §4.3.2 — close with HTTP 404\n // before the WebSocket handshake completes (no socket exposed to\n // the client).\n socket.write('HTTP/1.1 404 Not Found\\r\\nConnection: close\\r\\n\\r\\n');\n socket.destroy();\n return;\n }\n wss.handleUpgrade(req, socket, head, (ws) => {\n void onConnect(ws, req, cfg).catch((err) => {\n logger.error(\n `WebSocket $connect dispatch failed: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n try {\n ws.close(1011, 'internal error');\n } catch {\n /* socket may already be closed */\n }\n });\n });\n };\n opts.httpServer.on('upgrade', upgradeListener);\n\n // Maximum number of message frames buffered between handshake\n // completion and the `$connect` verdict. Bounds the memory-allocation\n // DoS surface against an unauthenticated client that spams frames\n // during the brief await window: we store the raw Buffer ref only —\n // no `bufferToBody` allocation work — and cap the buffer length at\n // this number. Excess frames are dropped (a warn line names the\n // overflow once per connection).\n //\n // Trade-off: 100 frames × ~4 KiB typical text frame ≈ ~400 KiB upper\n // bound per connection (~40 MiB for the default 100-server total) —\n // small enough that an attacker cannot exhaust memory by opening many\n // connections, large enough that a legitimate burst client (e.g. a\n // chat UI sending its initial state on connect) absorbs ~1s of\n // pre-verdict traffic at typical 100 frame/s rates without dropping.\n const MAX_PRE_VERDICT_FRAMES = 100;\n\n // Per-connection driver. The lifecycle is:\n // 1. Attach a TINY pre-verdict listener that stores raw frame refs\n // only (no bufferToBody allocation work).\n // 2. Invoke the `$connect` Lambda and await the verdict.\n // 3a. On allow: detach pre-listener, register the connection,\n // attach the real `message` / `close` / `error` listeners, then\n // synchronously drain the pre-verdict buffer through the real\n // dispatcher.\n // 3b. On deny: detach pre-listener, drop the buffer, close the\n // socket with policy-violation (1008). No registry entry, no\n // real listeners ever attached — defends against the DoS\n // surface where a denied client floods frames in the\n // close-handshake window.\n //\n // This is the structural fix for the pre-fix listener-leak bug:\n // previously the `message` listener was attached BEFORE the await\n // and called `bufferToBody` (allocating Buffers) on every incoming\n // frame, only short-circuiting on the verdict AFTER the allocation\n // ran. Even after `connectVerdict = 'denied'`, the listener stayed\n // attached and kept doing allocation work until the socket fully\n // closed.\n const onConnect = async (\n ws: WebSocket,\n req: IncomingMessage,\n cfg: WebSocketApiConfig\n ): Promise<void> => {\n const connectionId = randomUUID();\n const connectedAt = Date.now();\n const handshakeSnapshot = buildHandshakeSnapshot(req);\n const connectEvent = buildConnectEvent({\n connectionId,\n connectedAt,\n stage: cfg.api.stage,\n snapshot: handshakeSnapshot,\n });\n\n // Pre-verdict frame buffer. Stores RAW refs only — no allocation\n // work. Bounded by MAX_PRE_VERDICT_FRAMES to close the DoS surface.\n const preVerdictFrames: Array<{ raw: Buffer | ArrayBuffer | Buffer[]; isBinary: boolean }> = [];\n let preVerdictOverflow = false;\n let preVerdictClosed = false;\n\n const preListener = (raw: Buffer | ArrayBuffer | Buffer[], isBinary: boolean): void => {\n if (preVerdictClosed) return;\n if (preVerdictFrames.length >= MAX_PRE_VERDICT_FRAMES) {\n if (!preVerdictOverflow) {\n preVerdictOverflow = true;\n logger.warn(\n `WebSocket connection ${connectionId}: pre-verdict message buffer overflowed (>${MAX_PRE_VERDICT_FRAMES} frames). Excess frames dropped — client is sending faster than the $connect handler can resolve. (api=${cfg.api.declaredAt})`\n );\n }\n return;\n }\n preVerdictFrames.push({ raw, isBinary });\n };\n ws.on('message', preListener);\n\n // `close` / `error` listeners are intentionally NOT attached during\n // the await. A client disconnect during the $connect window\n // emits 'close' to no listener (no $disconnect Lambda fires —\n // matches AWS-deployed semantics where a connection that never\n // completed $connect never fires $disconnect either).\n\n // Find the `$connect` route. AWS treats `$connect` as optional —\n // when absent, the connection is admitted without invoking any\n // Lambda. Match deployed behavior.\n const connectRoute = cfg.api.routes.find((r) => r.routeKey === '$connect');\n if (connectRoute) {\n const allowed = await invokeRouteAndDecideAuth(\n connectRoute.targetLambdaLogicalId,\n connectEvent,\n opts.pool,\n rieTimeoutMs\n );\n if (!allowed) {\n // Deny — close the upgrade with policy violation (1008) and\n // do NOT register the connection. Detach the pre-listener\n // immediately and drop every buffered frame so no further\n // allocation work runs during the close-handshake window.\n preVerdictClosed = true;\n preVerdictFrames.length = 0;\n ws.off('message', preListener);\n try {\n ws.close(1008, 'Forbidden');\n } catch {\n /* ignore */\n }\n logger.debug(\n `WebSocket $connect denied for connection ${connectionId} on ${cfg.api.declaredAt}`\n );\n return;\n }\n }\n\n // Verdict = allowed. Detach the pre-listener BEFORE checking\n // readyState so a close that lands mid-this-function doesn't get\n // recorded in the pre-buffer.\n preVerdictClosed = true;\n ws.off('message', preListener);\n\n // Did the client disconnect during the $connect await? When yes,\n // the close event already fired and was dropped (we hadn't\n // attached `close` yet). Skip registration + skip $disconnect\n // dispatch (matches AWS-deployed semantics).\n if (ws.readyState !== ws.OPEN) {\n preVerdictFrames.length = 0;\n logger.debug(\n `WebSocket connection ${connectionId} closed during $connect await (readyState=${ws.readyState}) — skipping registration`\n );\n return;\n }\n\n const entry: ConnectionRegistryEntry = {\n connectionId,\n socket: ws,\n connectedAt,\n apiLogicalId: cfg.api.apiLogicalId,\n stage: cfg.api.stage,\n };\n registry.register(entry);\n logger.debug(\n `WebSocket connected: ${connectionId} (${cfg.api.declaredAt}, stage=${cfg.api.stage})`\n );\n\n // Attach the real listeners NOW. From this point forward, the\n // standard dispatch path runs for every incoming frame.\n //\n // Per-connection dispatch is SERIALIZED via `enqueueDispatch` (Issue\n // #527 M1). Each new frame chains onto the previous one's promise,\n // so handler N+1 starts only after handler N returns — matching\n // AWS-deployed semantics.\n ws.on('message', (raw, isBinary) => {\n const { body, isBase64Encoded } = websocketBody.bufferToBody(raw, isBinary);\n logger.debug(\n `WebSocket message received for connection ${connectionId}: ${body.slice(0, 200)}`\n );\n void enqueueDispatch(connectionId, () =>\n dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch(\n (err) => {\n logger.error(\n `WebSocket message dispatch failed (connection ${connectionId}): ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n try {\n ws.send(\n JSON.stringify({\n message: 'Internal server error',\n connectionId,\n requestId: randomUUID(),\n })\n );\n } catch {\n /* socket may already be closed */\n }\n }\n )\n );\n });\n ws.on('close', (code, reason) => {\n const reasonText = reason.toString('utf-8');\n // $disconnect runs AFTER any in-flight message dispatch for this\n // connection completes — same chain ensures the handler observes\n // a consistent end-of-stream rather than racing the last frame.\n // Track the promise so close()'s graceful-shutdown drain (Issue\n // #527 M3) can bound how long it waits for a hung handler.\n const disconnectPromise = enqueueDispatch(connectionId, () =>\n onDisconnect(connectionId, cfg, handshakeSnapshot, code, reasonText).catch((err) => {\n logger.warn(\n `WebSocket $disconnect dispatch failed (connection ${connectionId}): ${err instanceof Error ? err.message : String(err)}`\n );\n })\n );\n inFlightDisconnects.add(disconnectPromise);\n void disconnectPromise.finally(() => {\n inFlightDisconnects.delete(disconnectPromise);\n });\n });\n ws.on('error', (err) => {\n logger.debug(\n `WebSocket error for connection ${connectionId}: ${err instanceof Error ? err.message : String(err)}`\n );\n });\n\n // Drain any frames buffered pre-verdict via the same per-connection\n // chain so they dispatch in arrival order ahead of any new frames\n // that land while the drain is in flight. bufferToBody allocation\n // runs here (post-admit, bounded count) not at frame-receive time\n // (pre-admit, unbounded by attacker).\n for (const frame of preVerdictFrames) {\n const { body, isBase64Encoded } = websocketBody.bufferToBody(frame.raw, frame.isBinary);\n void enqueueDispatch(connectionId, () =>\n dispatchMessage(connectionId, cfg, body, isBase64Encoded, handshakeSnapshot).catch(\n (err) => {\n logger.error(\n `WebSocket buffered-message dispatch failed (connection ${connectionId}): ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n }\n )\n );\n }\n preVerdictFrames.length = 0;\n };\n\n const dispatchMessage = async (\n connectionId: string,\n cfg: WebSocketApiConfig,\n body: string,\n isBase64Encoded: boolean,\n snapshot: WebSocketHandshakeSnapshot\n ): Promise<void> => {\n const entry = registry.get(connectionId);\n if (!entry) return;\n\n const routeKey = selectRouteKey(cfg.api, body);\n const route = cfg.api.routes.find((r) => r.routeKey === routeKey);\n if (!route) {\n // Match AWS-deployed behavior: emit the error frame, keep socket\n // open. The socket reaches this branch only when neither the\n // selected key NOR `$default` matched.\n try {\n entry.socket.send(\n JSON.stringify({\n message: 'Internal server error',\n connectionId,\n requestId: randomUUID(),\n })\n );\n } catch {\n /* socket may have closed mid-error */\n }\n return;\n }\n const event = buildMessageEvent({\n connectionId,\n connectedAt: entry.connectedAt,\n stage: entry.stage,\n snapshot,\n routeKey,\n body,\n isBase64Encoded,\n });\n // AWS-deployed WebSocket APIs invoke the handler then DISCARD the\n // response; handler code MUST use `PostToConnection` to reply.\n // Match that exactly — invoke for side-effects, ignore return.\n await invokeRoute(route.targetLambdaLogicalId, event, opts.pool, rieTimeoutMs);\n };\n\n const onDisconnect = async (\n connectionId: string,\n cfg: WebSocketApiConfig,\n snapshot: WebSocketHandshakeSnapshot,\n code: number,\n reason: string\n ): Promise<void> => {\n const entry = registry.unregister(connectionId);\n if (!entry) return;\n logger.debug(\n `WebSocket disconnected: ${connectionId} (code=${code}, reason=${reason || '<none>'})`\n );\n const disconnectRoute = cfg.api.routes.find((r) => r.routeKey === '$disconnect');\n if (!disconnectRoute) return;\n const event = buildDisconnectEvent({\n connectionId,\n connectedAt: entry.connectedAt,\n stage: entry.stage,\n snapshot,\n disconnectStatusCode: code,\n disconnectReason: reason,\n });\n await invokeRoute(disconnectRoute.targetLambdaLogicalId, event, opts.pool, rieTimeoutMs);\n };\n\n // Bounded total drain window for in-flight $disconnect dispatches\n // during graceful shutdown (Issue #527 M3). 5s is long enough for a\n // well-behaved handler to flush logs / close db connections, short\n // enough that a hung handler can't hold the process open for\n // rieTimeoutMs (60s+) waiting on SIGKILL or the verify.sh 120s grace.\n const SHUTDOWN_DRAIN_MS = 5_000;\n\n let closed = false;\n return {\n registry,\n apiPaths,\n close: async (): Promise<void> => {\n if (closed) return;\n closed = true;\n opts.httpServer.off('upgrade', upgradeListener);\n // Close every live socket. The `close` handler each socket\n // installed will fire `$disconnect` per connection. Await every\n // closure via the WebSocketServer's tracked clients.\n const clients: WebSocket[] = Array.from(wss.clients);\n const closes = clients.map(\n (ws) =>\n new Promise<void>((resolve) => {\n const onClose = (): void => resolve();\n ws.once('close', onClose);\n try {\n ws.close(1001, 'going away');\n } catch {\n resolve();\n }\n // Defensive timeout so a stuck socket never hangs shutdown.\n setTimeout(() => {\n ws.off('close', onClose);\n resolve();\n }, 5_000).unref();\n })\n );\n await Promise.all(closes);\n // Drain in-flight $disconnect dispatches with a bounded ceiling.\n // A handler that exceeds SHUTDOWN_DRAIN_MS keeps running in the\n // background but no longer blocks the process exit.\n if (inFlightDisconnects.size > 0) {\n const drainStartCount = inFlightDisconnects.size;\n const drainComplete = Promise.allSettled(Array.from(inFlightDisconnects));\n const drainResult = await Promise.race([\n drainComplete.then(() => 'complete' as const),\n new Promise<'timeout'>((resolve) =>\n setTimeout(() => resolve('timeout'), SHUTDOWN_DRAIN_MS).unref()\n ),\n ]);\n if (drainResult === 'timeout' && inFlightDisconnects.size > 0) {\n logger.warn(\n `WebSocket graceful shutdown drained for ${SHUTDOWN_DRAIN_MS}ms; ` +\n `${inFlightDisconnects.size}/${drainStartCount} $disconnect handlers still in flight — leaking past shutdown.`\n );\n }\n }\n await new Promise<void>((resolve) => {\n wss.close(() => resolve());\n });\n },\n };\n}\n\n/**\n * Pre-pass for the HTTP `request` listener: intercept `POST/GET/DELETE\n * /@connections/<id>` calls and route them to the connection registry.\n *\n * Returns `true` when the request was handled (caller short-circuits\n * the normal HTTP dispatch path), `false` when the URL didn't match.\n *\n * The CLI installs this BEFORE the existing http-server pipeline so a\n * Lambda inside a container can call\n * `apigatewaymanagementapi:PostToConnection` and have cdk-local deliver the\n * message back to the open WebSocket without the request hitting the\n * route table.\n */\nexport async function handleManagementRequest(\n req: IncomingMessage,\n res: ServerResponse,\n registry: ConnectionRegistry\n): Promise<boolean> {\n const url = req.url ?? '';\n if (parseConnectionsPath(url) === null) return false;\n await handleConnectionsRequest({ req, res, registry });\n return true;\n}\n\n/**\n * Select the route the client message dispatches to.\n *\n * Algorithm (matches AWS docs §\"Selection expressions\"):\n * 1. Try to parse the body as JSON. Non-JSON → `$default`.\n * 2. Walk the selection-expression's JSON-path tokens against the\n * parsed body. Missing intermediate keys → `$default`.\n * 3. The final value's `String()` representation is the route key.\n * 4. When that key has no matching route, fall back to `$default`.\n *\n * v1's selection-expression grammar is `$request.body.<key>` (with\n * optional nested dot access). Other shapes were rejected upstream at\n * discovery time.\n */\nfunction selectRouteKey(api: DiscoveredWebSocketApi, body: string): string {\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return '$default';\n }\n const tokens = parseSelectionExpressionPath(api.routeSelectionExpression);\n let cursor: unknown = parsed;\n for (const token of tokens) {\n if (cursor === null || typeof cursor !== 'object') return '$default';\n cursor = (cursor as Record<string, unknown>)[token];\n if (cursor === undefined) return '$default';\n }\n const candidate = String(cursor);\n if (api.routes.some((r) => r.routeKey === candidate)) return candidate;\n return '$default';\n}\n\n/**\n * Invoke a route's Lambda for side effects only (MESSAGE / DISCONNECT\n * paths). The Lambda's response is intentionally discarded — AWS-deployed\n * WebSocket APIs do the same; handlers reply via `PostToConnection`.\n */\nasync function invokeRoute(\n lambdaLogicalId: string,\n event: WebSocketLambdaEvent,\n pool: ContainerPool,\n rieTimeoutMs: number\n): Promise<void> {\n const handle = await pool.acquire(lambdaLogicalId);\n try {\n await invokeRie(handle.containerHost, handle.hostPort, event, rieTimeoutMs);\n } finally {\n pool.release(handle);\n }\n}\n\n/**\n * Invoke the `$connect` Lambda and decide whether to accept the\n * connection. AWS-deployed behavior: handler returns `{statusCode:\n * 200}` (or any 2xx) → allow; anything else (non-2xx, error envelope,\n * throw, timeout) → deny.\n */\nasync function invokeRouteAndDecideAuth(\n lambdaLogicalId: string,\n event: WebSocketLambdaEvent,\n pool: ContainerPool,\n rieTimeoutMs: number\n): Promise<boolean> {\n let result;\n try {\n const handle = await pool.acquire(lambdaLogicalId);\n try {\n result = await invokeRie(handle.containerHost, handle.hostPort, event, rieTimeoutMs);\n } finally {\n pool.release(handle);\n }\n } catch {\n return false;\n }\n // Lambda runtime error envelope. The Node Lambda runtime emits\n // `{errorMessage, errorType, stackTrace}` for thrown handlers.\n if (result.payload && typeof result.payload === 'object') {\n const obj = result.payload as Record<string, unknown>;\n if (typeof obj['errorMessage'] === 'string' && typeof obj['statusCode'] !== 'number') {\n return false;\n }\n const status = obj['statusCode'];\n if (typeof status === 'number') {\n return status >= 200 && status < 300;\n }\n }\n // No explicit statusCode → admit. Matches AWS-deployed lenience for\n // handlers that simply return `null` / `undefined` on `$connect`.\n return true;\n}\n\n/**\n * Snapshot the upgrade-request data the event-builders need. We capture\n * this ONCE at `$connect` and reuse it for every event on the same\n * connection — `requestContext.identity.sourceIp` etc. must stay\n * consistent across CONNECT / MESSAGE / DISCONNECT (matches AWS).\n */\nfunction buildHandshakeSnapshot(req: IncomingMessage): WebSocketHandshakeSnapshot {\n const headers: Record<string, string[]> = {};\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n headers[name] = Array.isArray(value) ? [...value] : [value];\n }\n const url = req.url ?? '/';\n const queryIdx = url.indexOf('?');\n const rawQueryString = queryIdx >= 0 ? url.slice(queryIdx + 1) : '';\n const { single, multi } = parseQueryString(rawQueryString);\n const userAgent =\n typeof req.headers['user-agent'] === 'string' ? req.headers['user-agent'] : undefined;\n const sourceIp = req.socket.remoteAddress;\n return {\n headers,\n rawQueryString,\n ...(Object.keys(single).length > 0 && { queryStringParameters: single }),\n ...(Object.keys(multi).length > 0 && { multiValueQueryStringParameters: multi }),\n ...(sourceIp !== undefined && { sourceIp }),\n ...(userAgent !== undefined && { userAgent }),\n };\n}\n\n/**\n * Parse a raw query string into single-value (last-wins per AWS) and\n * multi-value maps. Mirrors `route-discovery.ts:parseQueryStringSingular`'s\n * convention — duplicated locally rather than reaching across modules\n * because the WebSocket path is a thin slice that does not need the\n * full HTTP-API parser.\n */\nfunction parseQueryString(qs: string): {\n single: Record<string, string>;\n multi: Record<string, string[]>;\n} {\n const single: Record<string, string> = {};\n const multi: Record<string, string[]> = {};\n if (qs.length === 0) return { single, multi };\n for (const pair of qs.split('&')) {\n if (pair.length === 0) continue;\n const eq = pair.indexOf('=');\n const rawKey = eq >= 0 ? pair.slice(0, eq) : pair;\n const rawVal = eq >= 0 ? pair.slice(eq + 1) : '';\n const key = safeDecode(rawKey);\n const val = safeDecode(rawVal);\n if (key === null) continue;\n single[key] = val ?? '';\n (multi[key] ??= []).push(val ?? '');\n }\n return { single, multi };\n}\n\nfunction safeDecode(s: string): string | null {\n try {\n return decodeURIComponent(s.replace(/\\+/g, ' '));\n } catch {\n return null;\n }\n}\n\n// `bufferToBody` lives in `./websocket-body.ts` and is re-exported here\n// so existing callers (and the existing test surface) continue to read\n// from `websocket-server.js`. The internal callers below import via the\n// `* as websocketBody` namespace so the B4 regression test can spy on\n// the export and intercept every internal call — Issue #537 item 6.\nexport { bufferToBody } from './websocket-body.js';\n","import { runDockerStreaming } from '../utils/docker-cmd.js';\n\n/**\n * Lower bound for `--add-host=<name>:host-gateway` support. The\n * `host-gateway` magic alias was introduced in Docker 20.10 (October\n * 2020) and is the load-bearing primitive cdk-local uses to let Lambda\n * containers reach the host's `cdkl start-api` server on Linux\n * native dockerd. Without it, the AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI\n * override fails with `ENOTFOUND host.docker.internal` at SDK-call time.\n *\n * Docker Desktop (macOS / Windows) ships `host.docker.internal` as\n * a built-in alias regardless of the engine version, but the probe\n * still fires there to keep the error path uniform — the `host-gateway`\n * flag itself is harmless on Docker Desktop.\n *\n * Issue #527 M2.\n */\nexport const HOST_GATEWAY_MIN_VERSION: ParsedDockerVersion = { major: 20, minor: 10, patch: 0 };\n\nexport interface ParsedDockerVersion {\n major: number;\n minor: number;\n patch: number;\n}\n\n/**\n * Parse a Docker server version string (`20.10.21` / `24.0.7-rd` /\n * `27.3.1+podman` etc.) into a comparable `{major, minor, patch}` tuple.\n * Returns `null` on any unparseable input — the caller treats that as\n * \"version unknown, skip the comparison and let the user proceed with\n * a warn\" rather than hard-failing on a Docker-compatible CLI binary\n * that doesn't follow Docker's version-string conventions\n * (e.g. podman / finch).\n */\nexport function parseDockerVersion(raw: string): ParsedDockerVersion | null {\n const trimmed = raw.trim();\n const match = /^(\\d+)\\.(\\d+)(?:\\.(\\d+))?/.exec(trimmed);\n if (!match) return null;\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: match[3] !== undefined ? Number(match[3]) : 0,\n };\n}\n\n/**\n * Compare two `ParsedDockerVersion` tuples. Returns negative when `a <\n * b`, zero when equal, positive when `a > b`. Patch-level differences\n * are part of the ordering so a future bump (e.g. 20.10.0 -> 20.10.5\n * to fix a CVE-related regression) can be expressed if needed.\n */\nexport function compareDockerVersions(a: ParsedDockerVersion, b: ParsedDockerVersion): number {\n if (a.major !== b.major) return a.major - b.major;\n if (a.minor !== b.minor) return a.minor - b.minor;\n return a.patch - b.patch;\n}\n\nexport interface HostGatewayProbeResult {\n /** Reported Docker server version string (\"20.10.21\" / \"27.3.1\" / etc.). */\n rawVersion: string;\n /** Parsed tuple, `null` when the raw string didn't match `<int>.<int>(.<int>)?`. */\n parsed: ParsedDockerVersion | null;\n /**\n * `true` when the parsed version is ≥ {@link HOST_GATEWAY_MIN_VERSION}\n * (or `null` parsed — see {@link parseDockerVersion} for the\n * unknown-version policy: defer to the warn path rather than hard-fail).\n */\n supported: boolean;\n}\n\n/**\n * Probe the Docker server's version to gate the `--add-host=...:host-gateway`\n * mapping that WebSocket Lambda containers need to reach the host\n * server. Issued ONCE per `cdkl start-api` invocation at WebSocket\n * attach time — HTTP-only / REST-only sessions skip the probe entirely.\n *\n * Throws when:\n * 1. The docker subprocess itself fails (binary missing, daemon down,\n * permission error) — the caller's catch surfaces the original\n * error so the user knows to install / start Docker.\n * 2. The probe succeeds but the parsed version is < the supported\n * minimum — caller decides whether to error or warn (the WebSocket\n * attach loop errors; HTTP-only sessions never call this).\n *\n * Implementation: `docker version --format '{{.Server.Version}}'`\n * returns the daemon's version (not the client's) so a brand-new\n * client against an old daemon is still caught.\n */\nexport async function probeHostGatewaySupport(): Promise<HostGatewayProbeResult> {\n const result = await runDockerStreaming(['version', '--format', '{{.Server.Version}}'], {\n streamLive: false,\n });\n const rawVersion = result.stdout.trim();\n const parsed = parseDockerVersion(rawVersion);\n // Empty stdout is treated as `supported=false` — `docker version\n // --format '{{.Server.Version}}'` returning an empty string is much\n // more likely a broken probe (daemon unreachable, permission stripped\n // output, format string mismatch on a forked CLI) than a podman /\n // finch shape. Masking it as \"unknown engine, proceed\" would defeat\n // the whole point of the probe — the caller's error path surfaces\n // the empty string explicitly so the user can diagnose the\n // underlying daemon failure. Surfaced by PR #539 review.\n if (rawVersion === '') {\n return { rawVersion, parsed: null, supported: false };\n }\n // Treat unparseable-but-non-empty versions as \"supported\" — podman /\n // finch / nerdctl emit version strings cdk-local can't always compare\n // against Docker's. Defer to the warn path rather than refuse the\n // boot.\n const supported = parsed === null || compareDockerVersions(parsed, HOST_GATEWAY_MIN_VERSION) >= 0;\n return { rawVersion, parsed, supported };\n}\n","/**\n * AWS API Gateway VTL (Velocity Template Language) evaluator — hand-rolled\n * minimal subset for `cdkl start-api`'s REST v1 non-AWS_PROXY\n * integrations (#457).\n *\n * Background\n * ----------\n *\n * AWS API Gateway uses VTL to map between HTTP request / response shapes\n * and integration backend (Lambda non-proxy / HTTP / MOCK / AWS service)\n * request / response shapes. The full VTL spec is large; AWS API Gateway\n * exposes a SUBSET plus three AWS-specific built-in variables (`$input`,\n * `$context`, `$util`). cdk-local implements the SUBSET that real CDK apps\n * use in practice — anything outside it surfaces a clear error rather\n * than silently producing wrong output. See the\n * `VtlEvaluationError.message` field for the exact name when something\n * is unsupported.\n *\n * Supported VTL features\n * ----------------------\n *\n * - Variable references: `$var` / `${var}` / `$obj.field` /\n * `$obj.field.subField` (Velocity property notation).\n * - Method calls on built-ins: `$input.body` / `$input.json('$.path')` /\n * `$input.path('$.path')` / `$input.params()` / `$input.params('name')` /\n * `$input.params('header')` / `$context.*` / `$util.escapeJavaScript(x)` /\n * `$util.base64Encode(x)` / `$util.base64Decode(x)` / `$util.urlEncode(x)` /\n * `$util.urlDecode(x)` / `$util.parseJson(x)`.\n * - `#set($var = expr)` directives.\n * - `#if(cond) ... #elseif(cond) ... #else ... #end` blocks.\n * - `#foreach($x in $list) ... #end` loops.\n * - String / number / boolean / null literals: `\"text\"`, `'text'`, `42`,\n * `3.14`, `true`, `false`, `null`.\n * - Logical operators: `&&`, `||`, `!`.\n * - Comparison operators: `==`, `!=`, `<`, `<=`, `>`, `>=`.\n * - Implicit string concatenation (literal text + interpolated `$var`).\n *\n * NOT supported (intentionally)\n * -----------------------------\n *\n * - User-defined macros (`#macro`).\n * - `#parse` / `#include`.\n * - Velocity's arithmetic operators (`+ - * /`) outside literal concat.\n * - Range operator (`[1..5]`).\n * - `$velocityCount` and other Velocity context built-ins.\n *\n * AWS API Gateway-specific bindings\n * ---------------------------------\n *\n * `$input.body` — the raw request body as a string.\n * `$input.json('$.path.to.field')` — JSONPath against the body,\n * returned as a JSON-stringified value (so primitives are JSON-quoted).\n * `$input.path('$.path.to.field')` — JSONPath against the body, returned\n * as the native value (primitives unquoted).\n * `$input.params()` — `{header: {...}, querystring: {...}, path: {...}}`.\n * `$input.params('name')` — order: path > query > header (deployed\n * behavior; AWS docs).\n * `$input.params('header').<name>` — header lookup.\n * `$input.params('querystring').<name>` — querystring lookup.\n * `$input.params('path').<name>` — path-parameter lookup.\n *\n * `$context.requestId` — synthesized per request.\n * `$context.identity.sourceIp` — request client IP.\n * `$context.identity.userAgent` — request user agent.\n * `$context.httpMethod` — request method.\n * `$context.resourcePath` — the route's pathPattern.\n * `$context.stage` — the route's stage name (`$default` if no stage).\n *\n * `$util.escapeJavaScript(s)` — escape `\\`, `'`, `\"`, `\\n`, `\\r`, `\\t`\n * for embedding inside a JavaScript string literal.\n * `$util.base64Encode(s)` — base64 (no padding stripped).\n * `$util.base64Decode(s)` — UTF-8 decode of base64.\n * `$util.urlEncode(s)` / `$util.urlDecode(s)` — RFC 3986 encoding.\n * `$util.parseJson(s)` — `JSON.parse(s)`.\n *\n * Mismatches between cdk-local's evaluator and AWS-deployed VTL surface as\n * `VtlEvaluationError`. Spurious whitespace / formatting differences are\n * acceptable and not in the contract.\n */\n\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Bindings available to a VTL template evaluation. Built up by\n * `buildVtlContext` from an `HttpRequestSnapshot` + `MatchedRouteContext`.\n */\nexport interface VtlContext {\n /** `$input` — the request body + parameter accessor. */\n input: VtlInput;\n /** `$context` — request metadata (requestId, identity, etc.). */\n context: VtlRequestContext;\n /**\n * `$util` — utility functions (escape, base64, URL encoding, JSON parse).\n * The `util` reference is shared across templates; pure-functional, no\n * shared state.\n */\n util: VtlUtil;\n /**\n * Additional bindings for response-side templates. The Lambda integration\n * type binds `$inputRoot` to the parsed return value (AWS docs convention\n * for response mapping templates).\n */\n inputRoot?: unknown;\n}\n\n/** `$input` binding — request body + parameter accessor. */\nexport interface VtlInput {\n /** Raw request body as a string. */\n body: string;\n /** Parsed JSON of `body` (computed lazily — read-only here). */\n jsonBody: unknown;\n /** Header map (single-value form; comma-joined when AWS emits multi-value). */\n headers: Record<string, string>;\n /** Query-string map (single-value form). */\n querystring: Record<string, string>;\n /** Path-parameter map. */\n path: Record<string, string>;\n}\n\n/** `$context` binding — synthesized per request. */\nexport interface VtlRequestContext {\n requestId: string;\n httpMethod: string;\n resourcePath: string;\n stage: string;\n identity: {\n sourceIp: string;\n userAgent: string;\n };\n}\n\n/** `$util` binding — built-in functions. */\nexport interface VtlUtil {\n escapeJavaScript(input: unknown): string;\n base64Encode(input: unknown): string;\n base64Decode(input: unknown): string;\n urlEncode(input: unknown): string;\n urlDecode(input: unknown): string;\n parseJson(input: unknown): unknown;\n}\n\n/** Error thrown when a template references an unsupported VTL feature. */\nexport class VtlEvaluationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'VtlEvaluationError';\n Object.setPrototypeOf(this, VtlEvaluationError.prototype);\n }\n}\n\n/** Built-in `$util` implementation. */\nexport function buildDefaultUtil(): VtlUtil {\n const coerce = (v: unknown): string => {\n if (v == null) return '';\n if (typeof v === 'string') return v;\n if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') {\n return String(v);\n }\n // Object / array — JSON-stringify to avoid the `[object Object]` trap.\n try {\n return JSON.stringify(v);\n } catch {\n return '';\n }\n };\n return {\n escapeJavaScript(input) {\n // Mirror AWS API Gateway behavior: escape backslash, single + double\n // quote, and common control chars. Forward slash is NOT escaped\n // (mismatches some online references; AWS-deployed behavior is the\n // arbiter).\n return coerce(input)\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/'/g, \"\\\\'\")\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n .replace(/\\t/g, '\\\\t');\n },\n base64Encode(input) {\n return Buffer.from(coerce(input), 'utf-8').toString('base64');\n },\n base64Decode(input) {\n return Buffer.from(coerce(input), 'base64').toString('utf-8');\n },\n urlEncode(input) {\n return encodeURIComponent(coerce(input));\n },\n urlDecode(input) {\n try {\n return decodeURIComponent(coerce(input));\n } catch {\n return coerce(input);\n }\n },\n parseJson(input) {\n const s = coerce(input);\n try {\n return JSON.parse(s);\n } catch (err) {\n throw new VtlEvaluationError(\n `$util.parseJson: invalid JSON input: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n },\n };\n}\n\n/**\n * Public entry point — evaluate a VTL template against a context and\n * return the rendered string. Throws {@link VtlEvaluationError} on any\n * unsupported syntax or runtime failure.\n *\n * Empty / undefined templates short-circuit to an empty string, matching\n * AWS API Gateway behavior when `RequestTemplates` / `ResponseTemplates`\n * is absent for the selected content type.\n */\nexport function evaluateVtl(template: string | undefined, ctx: VtlContext): string {\n if (template === undefined || template.length === 0) return '';\n const evaluator = new VtlEvaluator(ctx);\n return evaluator.evaluate(template);\n}\n\n// ===================== Internal implementation ============================\n\n/**\n * Stateful evaluator. Tokenizes + parses + renders in one pass — minimal\n * subset, so a recursive-descent walk over the template suffices. Tracks\n * a per-template scope chain for `#set` and `#foreach` bindings.\n */\nclass VtlEvaluator {\n private readonly ctx: VtlContext;\n private readonly scopes: Array<Map<string, unknown>>;\n private readonly output: string[] = [];\n\n constructor(ctx: VtlContext) {\n this.ctx = ctx;\n this.scopes = [new Map()];\n }\n\n evaluate(template: string): string {\n this.renderBlock(template);\n return this.output.join('');\n }\n\n /**\n * Render a block — walks the template, interpolating `${var}` /\n * `$var.field.method(args)` and handling `#set` / `#if` / `#foreach`\n * directives.\n *\n * The walk is line-aware for directives: every `#directive` MUST start\n * a line (after whitespace) per Velocity convention, but for ergonomics\n * we also accept directives at the start of the template. Inline `$var`\n * references are handled anywhere.\n */\n private renderBlock(block: string): void {\n let i = 0;\n while (i < block.length) {\n const ch = block[i];\n if (ch === '#' && this.isDirectiveStart(block, i)) {\n i = this.handleDirective(block, i);\n continue;\n }\n if (ch === '$') {\n const consumed = this.handleVariable(block, i);\n if (consumed > 0) {\n i += consumed;\n continue;\n }\n }\n if (ch === '\\\\' && i + 1 < block.length && block[i + 1] === '$') {\n // Escaped `\\$` — emit literal `$`.\n this.output.push('$');\n i += 2;\n continue;\n }\n this.output.push(ch ?? '');\n i++;\n }\n }\n\n private isDirectiveStart(block: string, i: number): boolean {\n // `#` must be followed by an alpha (or `*` for comments — unsupported).\n if (i + 1 >= block.length) return false;\n const next = block[i + 1];\n if (next === '#') return true; // single-line comment `##`\n return next !== undefined && /[a-zA-Z]/.test(next);\n }\n\n /**\n * Handle one directive (`#set`, `#if`, `#foreach`, etc.) — returns the\n * NEW index in `block` (i.e. how far we consumed past the directive).\n */\n private handleDirective(block: string, start: number): number {\n // Single-line `##` comment.\n if (block[start + 1] === '#') {\n const eol = block.indexOf('\\n', start);\n return eol === -1 ? block.length : eol + 1;\n }\n\n const directiveMatch = /^#([a-zA-Z]+)/.exec(block.slice(start));\n if (!directiveMatch) {\n this.output.push('#');\n return start + 1;\n }\n const name = directiveMatch[1]!;\n const afterDirective = start + 1 + name.length;\n\n switch (name) {\n case 'set':\n return this.handleSetDirective(block, afterDirective);\n case 'if':\n return this.handleIfDirective(block, afterDirective);\n case 'foreach':\n return this.handleForeachDirective(block, afterDirective);\n case 'else':\n case 'elseif':\n case 'end':\n // Bare `#else` / `#elseif` / `#end` outside their parent — caller\n // already consumed these via `consumeUntilEnd`. If we see them\n // here it's a template-author error.\n throw new VtlEvaluationError(`Unexpected #${name} outside of a #if / #foreach block`);\n default:\n throw new VtlEvaluationError(\n `Unsupported VTL directive #${name} (${getEmbedConfig().cliName} start-api supports #set / #if / #elseif / #else / #foreach / #end / ##)`\n );\n }\n }\n\n /**\n * `#set($var = expression)` — assigns to the innermost scope.\n */\n private handleSetDirective(block: string, after: number): number {\n const { args, end } = this.readParenArgs(block, after);\n const eq = args.indexOf('=');\n if (eq === -1) {\n throw new VtlEvaluationError(`#set requires '=': got #set(${args})`);\n }\n const left = args.slice(0, eq).trim();\n const right = args.slice(eq + 1).trim();\n if (!left.startsWith('$')) {\n throw new VtlEvaluationError(`#set left side must be a $var reference (got '${left}')`);\n }\n const varName = left.slice(1).replace(/^\\{/, '').replace(/\\}$/, '');\n const value = this.evaluateExpression(right);\n this.scopes[this.scopes.length - 1]!.set(varName, value);\n return this.skipDirectiveTrailingNewline(block, end);\n }\n\n /**\n * `#if (cond) ... #elseif (cond) ... #else ... #end`. Renders the\n * first true branch only; the rest are skipped (their text NOT emitted).\n */\n private handleIfDirective(block: string, after: number): number {\n const { args: condExpr, end } = this.readParenArgs(block, after);\n let rendered = false;\n let renderedAny = false;\n\n // Find the matching #end (with #else / #elseif handling).\n const branches: Array<{ condition: string | null; bodyStart: number; bodyEnd: number }> = [\n {\n condition: condExpr,\n bodyStart: this.skipDirectiveTrailingNewline(block, end),\n bodyEnd: -1,\n },\n ];\n let cursor = branches[0]!.bodyStart;\n let depth = 1;\n while (cursor < block.length && depth > 0) {\n if (block[cursor] !== '#') {\n cursor++;\n continue;\n }\n const m = /^#([a-zA-Z]+)/.exec(block.slice(cursor));\n if (!m) {\n cursor++;\n continue;\n }\n const tag = m[1]!;\n const tagAfter = cursor + 1 + tag.length;\n if (tag === 'if' || tag === 'foreach') {\n depth++;\n cursor = tagAfter;\n continue;\n }\n if (tag === 'end') {\n depth--;\n if (depth === 0) {\n branches[branches.length - 1]!.bodyEnd = cursor;\n const endIdx = this.skipDirectiveTrailingNewline(block, tagAfter);\n // Render the first true branch.\n for (const branch of branches) {\n if (rendered) break;\n const truthy: boolean =\n branch.condition === null ? !renderedAny : this.evaluateCondition(branch.condition);\n if (truthy) {\n this.renderBlock(block.slice(branch.bodyStart, branch.bodyEnd));\n rendered = true;\n }\n renderedAny = renderedAny || truthy;\n }\n return endIdx;\n }\n cursor = tagAfter;\n continue;\n }\n if (depth === 1 && (tag === 'elseif' || tag === 'else')) {\n branches[branches.length - 1]!.bodyEnd = cursor;\n if (tag === 'elseif') {\n const { args, end: elseifEnd } = this.readParenArgs(block, tagAfter);\n branches.push({\n condition: args,\n bodyStart: this.skipDirectiveTrailingNewline(block, elseifEnd),\n bodyEnd: -1,\n });\n cursor = branches[branches.length - 1]!.bodyStart;\n } else {\n branches.push({\n condition: null,\n bodyStart: this.skipDirectiveTrailingNewline(block, tagAfter),\n bodyEnd: -1,\n });\n cursor = branches[branches.length - 1]!.bodyStart;\n }\n continue;\n }\n cursor = tagAfter;\n }\n throw new VtlEvaluationError('#if without matching #end');\n }\n\n /**\n * `#foreach($x in $list) ... #end` — iterates a list / object's values.\n */\n private handleForeachDirective(block: string, after: number): number {\n const { args, end } = this.readParenArgs(block, after);\n const m = /^\\s*\\$([a-zA-Z_][a-zA-Z_0-9]*)\\s+in\\s+(.+)$/.exec(args);\n if (!m) {\n throw new VtlEvaluationError(`Invalid #foreach syntax: ${args}`);\n }\n const varName = m[1]!;\n const listExpr = m[2]!;\n const listValue = this.evaluateExpression(listExpr);\n\n let depth = 1;\n let cursor = this.skipDirectiveTrailingNewline(block, end);\n const bodyStart = cursor;\n while (cursor < block.length && depth > 0) {\n if (block[cursor] !== '#') {\n cursor++;\n continue;\n }\n const tm = /^#([a-zA-Z]+)/.exec(block.slice(cursor));\n if (!tm) {\n cursor++;\n continue;\n }\n const tag = tm[1]!;\n if (tag === 'if' || tag === 'foreach') {\n depth++;\n cursor += 1 + tag.length;\n continue;\n }\n if (tag === 'end') {\n depth--;\n if (depth === 0) {\n const bodyEnd = cursor;\n const endIdx = this.skipDirectiveTrailingNewline(block, cursor + 1 + tag.length);\n\n const items = this.coerceToIterable(listValue);\n for (const item of items) {\n this.scopes.push(new Map([[varName, item]]));\n try {\n this.renderBlock(block.slice(bodyStart, bodyEnd));\n } finally {\n this.scopes.pop();\n }\n }\n return endIdx;\n }\n }\n cursor += 1 + tag.length;\n }\n throw new VtlEvaluationError('#foreach without matching #end');\n }\n\n /** Convert a value into an iterable sequence for `#foreach`. */\n private coerceToIterable(value: unknown): unknown[] {\n if (Array.isArray(value)) return value;\n if (value && typeof value === 'object') return Object.values(value as Record<string, unknown>);\n if (value == null) return [];\n return [value];\n }\n\n /**\n * Skip whitespace and a single trailing newline immediately after a\n * directive — matches Velocity's \"directive eats its own newline\"\n * convention. Without this rule, every `#set(...)` line in a template\n * would leave a blank line in the output.\n */\n private skipDirectiveTrailingNewline(block: string, after: number): number {\n let i = after;\n while (i < block.length && (block[i] === ' ' || block[i] === '\\t')) i++;\n if (block[i] === '\\r') i++;\n if (block[i] === '\\n') i++;\n return i;\n }\n\n /**\n * Read `(...)` arguments after a directive name. Returns the inner\n * string + the index AFTER the closing paren. Handles nested parens\n * inside string literals / method calls.\n */\n private readParenArgs(block: string, after: number): { args: string; end: number } {\n let i = after;\n while (i < block.length && (block[i] === ' ' || block[i] === '\\t')) i++;\n if (block[i] !== '(') {\n throw new VtlEvaluationError(`Expected '(' after directive at offset ${after}`);\n }\n i++;\n let depth = 1;\n const start = i;\n let inString: '\"' | \"'\" | null = null;\n while (i < block.length && depth > 0) {\n const c = block[i];\n if (inString) {\n if (c === '\\\\' && i + 1 < block.length) {\n i += 2;\n continue;\n }\n if (c === inString) inString = null;\n i++;\n continue;\n }\n if (c === '\"' || c === \"'\") {\n inString = c;\n i++;\n continue;\n }\n if (c === '(') depth++;\n else if (c === ')') depth--;\n if (depth === 0) break;\n i++;\n }\n if (depth !== 0) {\n throw new VtlEvaluationError(`Unterminated parenthesised argument at offset ${after}`);\n }\n return { args: block.slice(start, i), end: i + 1 };\n }\n\n /**\n * Handle a `$var` / `${var}` / `$obj.field.method(args)` reference.\n * Returns the number of characters consumed (0 if not a reference —\n * caller emits the literal `$`).\n */\n private handleVariable(block: string, start: number): number {\n const m = /^\\$(\\{[^}]+\\}|[a-zA-Z_][a-zA-Z_0-9]*(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)*)/.exec(\n block.slice(start)\n );\n if (!m) return 0;\n const ref = m[1]!;\n const refStr = ref.startsWith('{') ? ref.slice(1, -1) : ref;\n let consumed = m[0].length;\n\n // After the chain, look for method call `(...)`. Repeat to chain\n // method calls (e.g. `$input.params('querystring').name`).\n let value = this.resolveReference(refStr);\n let pos = start + consumed;\n while (pos < block.length) {\n if (block[pos] === '(') {\n const { args, end } = this.readParenArgs(block, pos);\n value = this.callValueAsMethod(value, args, refStr);\n consumed = end - start;\n pos = end;\n // After a method call, allow a trailing `.field` chain like\n // `$input.params('querystring').name`.\n if (pos < block.length && block[pos] === '.') {\n const tailMatch = /^\\.([a-zA-Z_][a-zA-Z_0-9]*)/.exec(block.slice(pos));\n if (tailMatch) {\n const field = tailMatch[1]!;\n value = lookupField(value, field);\n consumed += tailMatch[0].length;\n pos += tailMatch[0].length;\n continue;\n }\n }\n break;\n }\n break;\n }\n\n this.output.push(this.stringifyForOutput(value));\n return consumed;\n }\n\n /**\n * Resolve a dotted reference path against context + scopes. The first\n * segment is matched against built-in roots (`input` / `context` / `util`\n * / `inputRoot`) and the scope chain in order.\n */\n private resolveReference(path: string): unknown {\n const parts = path.split('.');\n const first = parts[0]!;\n const rest = parts.slice(1);\n let base: unknown;\n if (first === 'input') base = this.ctx.input;\n else if (first === 'context') base = this.ctx.context;\n else if (first === 'util') base = this.ctx.util;\n else if (first === 'inputRoot') base = this.ctx.inputRoot;\n else {\n // Walk scope chain from innermost outward.\n let found = false;\n for (let i = this.scopes.length - 1; i >= 0; i--) {\n const scope = this.scopes[i]!;\n if (scope.has(first)) {\n base = scope.get(first);\n found = true;\n break;\n }\n }\n if (!found) {\n // Unknown variable returns `null` (Velocity convention — silent).\n return null;\n }\n }\n return rest.reduce<unknown>((acc, seg) => lookupField(acc, seg), base);\n }\n\n /**\n * Invoke a value as a method — used after a `$ref(args)` shape. The\n * value must be a function or a special-cased built-in.\n */\n private callValueAsMethod(value: unknown, argsRaw: string, refPath: string): unknown {\n if (typeof value !== 'function') {\n throw new VtlEvaluationError(\n `Reference '$${refPath}' is not callable (got ${typeof value}). ${getEmbedConfig().productName} supports calling $input / $util / $context method-style references only.`\n );\n }\n const args = this.parseArgList(argsRaw);\n const fn = value as (...a: unknown[]) => unknown;\n return fn(...args);\n }\n\n /**\n * Parse a comma-separated argument list — recursively evaluates each\n * expression. Handles string literals, numbers, booleans, and nested\n * `$var` refs.\n */\n private parseArgList(raw: string): unknown[] {\n const trimmed = raw.trim();\n if (trimmed.length === 0) return [];\n const parts: string[] = [];\n let depth = 0;\n let inString: '\"' | \"'\" | null = null;\n let start = 0;\n for (let i = 0; i < trimmed.length; i++) {\n const c = trimmed[i]!;\n if (inString) {\n if (c === '\\\\' && i + 1 < trimmed.length) {\n i++;\n continue;\n }\n if (c === inString) inString = null;\n continue;\n }\n if (c === '\"' || c === \"'\") {\n inString = c;\n continue;\n }\n if (c === '(' || c === '[') depth++;\n else if (c === ')' || c === ']') depth--;\n else if (c === ',' && depth === 0) {\n parts.push(trimmed.slice(start, i));\n start = i + 1;\n }\n }\n parts.push(trimmed.slice(start));\n return parts.map((p) => this.evaluateExpression(p.trim()));\n }\n\n /**\n * Evaluate a sub-expression (string literal / number / boolean / null /\n * `$ref` / `$ref.field`). Tiny grammar — no arithmetic operators.\n */\n private evaluateExpression(expr: string): unknown {\n const trimmed = expr.trim();\n if (trimmed.length === 0) return null;\n\n if (trimmed === 'true') return true;\n if (trimmed === 'false') return false;\n if (trimmed === 'null') return null;\n\n // String literal — single or double quotes.\n if (\n (trimmed.startsWith('\"') && trimmed.endsWith('\"')) ||\n (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\"))\n ) {\n return this.unescapeStringLiteral(trimmed.slice(1, -1));\n }\n\n // Number literal.\n if (/^-?\\d+(?:\\.\\d+)?$/.test(trimmed)) {\n return Number(trimmed);\n }\n\n // Variable reference (potentially with method call). Reuse the\n // variable handler by reading the whole thing.\n if (trimmed.startsWith('$')) {\n // Strip optional `${...}` wrapping.\n const refMatch = /^\\$(\\{[^}]+\\}|[a-zA-Z_][a-zA-Z_0-9]*(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)*)$/.exec(\n trimmed\n );\n if (refMatch) {\n const refStr = refMatch[1]!;\n const refPath = refStr.startsWith('{') ? refStr.slice(1, -1) : refStr;\n return this.resolveReference(refPath);\n }\n // Has trailing `(args)` — call the reference.\n const callMatch = /^\\$([a-zA-Z_][a-zA-Z_0-9]*(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)*)\\((.*)\\)$/.exec(\n trimmed\n );\n if (callMatch) {\n const refPath = callMatch[1]!;\n const argsRaw = callMatch[2]!;\n const value = this.resolveReference(refPath);\n return this.callValueAsMethod(value, argsRaw, refPath);\n }\n }\n throw new VtlEvaluationError(`Could not evaluate VTL sub-expression: '${trimmed}'`);\n }\n\n private unescapeStringLiteral(s: string): string {\n return s\n .replace(/\\\\n/g, '\\n')\n .replace(/\\\\r/g, '\\r')\n .replace(/\\\\t/g, '\\t')\n .replace(/\\\\\\\\/g, '\\\\')\n .replace(/\\\\\"/g, '\"')\n .replace(/\\\\'/g, \"'\");\n }\n\n /**\n * Evaluate a `#if` / `#elseif` condition expression. Supports `&&`,\n * `||`, `!`, comparison ops, and bare value tests (truthy/falsy).\n */\n private evaluateCondition(expr: string): boolean {\n const trimmed = expr.trim();\n // Tokenize at top-level `&&` / `||`.\n const orParts = splitTopLevel(trimmed, '||');\n if (orParts.length > 1) {\n return orParts.some((p) => this.evaluateCondition(p));\n }\n const andParts = splitTopLevel(trimmed, '&&');\n if (andParts.length > 1) {\n return andParts.every((p) => this.evaluateCondition(p));\n }\n if (trimmed.startsWith('!')) {\n return !this.evaluateCondition(trimmed.slice(1).trim());\n }\n // Parenthesised expression.\n if (trimmed.startsWith('(') && trimmed.endsWith(')')) {\n return this.evaluateCondition(trimmed.slice(1, -1));\n }\n // Comparison operators.\n for (const op of ['==', '!=', '<=', '>=', '<', '>'] as const) {\n const parts = splitTopLevel(trimmed, op);\n if (parts.length === 2) {\n const lhs = this.evaluateExpression(parts[0]!);\n const rhs = this.evaluateExpression(parts[1]!);\n return compareValues(lhs, rhs, op);\n }\n }\n // Bare value test — truthy unless null / false / empty string / 0.\n const value = this.evaluateExpression(trimmed);\n return isTruthy(value);\n }\n\n /**\n * Convert a value to its template output form. Mirrors Velocity's\n * `toString` convention: `null` → empty string; objects → JSON; numbers\n * / booleans → standard.\n */\n private stringifyForOutput(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n return JSON.stringify(value);\n }\n}\n\n// ===================== Helpers ============================================\n\n/**\n * Look up `field` on `obj`. Returns `null` for missing fields (Velocity\n * silent-undefined convention).\n */\nfunction lookupField(obj: unknown, field: string): unknown {\n if (obj == null) return null;\n if (typeof obj === 'object') {\n const rec = obj as Record<string, unknown>;\n if (Object.prototype.hasOwnProperty.call(rec, field)) return rec[field];\n // Allow camelCase / dot-style lookups against AWS-shape contexts.\n return null;\n }\n return null;\n}\n\nfunction splitTopLevel(s: string, sep: string): string[] {\n const out: string[] = [];\n let depth = 0;\n let inString: '\"' | \"'\" | null = null;\n let start = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s[i]!;\n if (inString) {\n if (c === '\\\\' && i + 1 < s.length) {\n i++;\n continue;\n }\n if (c === inString) inString = null;\n continue;\n }\n if (c === '\"' || c === \"'\") {\n inString = c;\n continue;\n }\n if (c === '(' || c === '[') depth++;\n else if (c === ')' || c === ']') depth--;\n else if (depth === 0 && s.startsWith(sep, i)) {\n out.push(s.slice(start, i));\n start = i + sep.length;\n i += sep.length - 1;\n }\n }\n out.push(s.slice(start));\n return out;\n}\n\nfunction compareValues(\n lhs: unknown,\n rhs: unknown,\n op: '==' | '!=' | '<' | '<=' | '>' | '>='\n): boolean {\n if (op === '==') return looseEqual(lhs, rhs);\n if (op === '!=') return !looseEqual(lhs, rhs);\n // Number / string comparison — coerce strings to numbers when possible.\n const a = typeof lhs === 'number' ? lhs : Number(lhs);\n const b = typeof rhs === 'number' ? rhs : Number(rhs);\n if (Number.isFinite(a) && Number.isFinite(b)) {\n switch (op) {\n case '<':\n return a < b;\n case '<=':\n return a <= b;\n case '>':\n return a > b;\n case '>=':\n return a >= b;\n }\n }\n const sa = String(lhs);\n const sb = String(rhs);\n switch (op) {\n case '<':\n return sa < sb;\n case '<=':\n return sa <= sb;\n case '>':\n return sa > sb;\n case '>=':\n return sa >= sb;\n }\n}\n\n/**\n * `==` / `!=` semantics for the VTL evaluator. Mirrors Apache Velocity's\n * lenient equality: same-type primitives compare via `===`; nested objects\n * / arrays compare structurally via canonical JSON; cross-type pairs (the\n * common case in AWS API Gateway templates, e.g. `#if($x == \"true\")` where\n * `$x` is a boolean from `$input.json('$.flag')`) fall back to comparing\n * each side coerced through {@link safeStringify}. The cross-type fall-back\n * is what Velocity itself does in this context, NOT JavaScript's\n * `==` operator (which has its own well-known foot-guns around `null` /\n * `undefined` / `NaN`). See the Velocity reference for the canonical\n * semantics: <https://velocity.apache.org/engine/2.0/vtl-reference.html#a3.4.4.RelationalOperators>\n * (look for the \"Equivalent\" / \"Not Equivalent\" rows).\n *\n * Both `null` and `undefined` collapse to a single sentinel — two\n * unset variables are considered equal. This matches Velocity's lenient\n * `null` handling (Issue (#507) item 8).\n */\nfunction looseEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return a == null && b == null;\n if (typeof a === typeof b) {\n if (typeof a === 'object') return JSON.stringify(a) === JSON.stringify(b);\n return false;\n }\n // Cross-type compare: AWS templates often write `$x == \"true\"` where\n // `$x` is a boolean; fall back to string compare. Use a defensive\n // stringifier that JSON-encodes objects rather than the default\n // `[object Object]` trap.\n return safeStringify(a) === safeStringify(b);\n}\n\nfunction safeStringify(v: unknown): string {\n if (v == null) return '';\n if (typeof v === 'string') return v;\n if (typeof v === 'number' || typeof v === 'boolean' || typeof v === 'bigint') return String(v);\n try {\n return JSON.stringify(v);\n } catch {\n return '';\n }\n}\n\nfunction isTruthy(v: unknown): boolean {\n if (v == null) return false;\n if (typeof v === 'boolean') return v;\n if (typeof v === 'number') return v !== 0;\n if (typeof v === 'string') return v.length > 0;\n if (Array.isArray(v)) return v.length > 0;\n if (typeof v === 'object') return Object.keys(v as object).length > 0;\n return true;\n}\n\n/**\n * Build a `VtlInput` binding from an HTTP request snapshot + matched\n * route context. `$input` exposes the body + parameter accessors used by\n * AWS API Gateway's VTL templates.\n *\n * `params()` returns the union of header / querystring / path maps;\n * `params(name)` resolves first against path, then querystring, then\n * header (matches AWS-deployed precedence).\n *\n * `json(jsonPath)` returns a JSON-stringified slice of the parsed body;\n * `path(jsonPath)` returns the raw native value (primitives unquoted).\n *\n * Case-sensitivity contract (Issue (#507) item 5; mirrored on the write\n * side by `rest-v1-integrations.ts` `resolveRequestParameterValue` /\n * `applyRequestParameters`):\n *\n * - `$input.params('<name>')` resolves PATH (case-sensitive) →\n * QUERYSTRING (case-sensitive, last-wins on duplicates) → HEADER\n * (case-insensitive — first the input's pre-lowercased map, then a\n * last-resort case-insensitive scan; multi-value duplicates are\n * comma-joined by the http-server before reaching VTL).\n * - `$input.params('header')` / `$input.params('querystring')` /\n * `$input.params('path')` return the raw category maps without\n * case-folding the keys further.\n *\n * JSONPath support is minimal: supports `$` (root), `$.field`,\n * `$.field.subField`, `$.array[0]`. AWS supports more (filter\n * expressions, recursive descent); cdk-local surfaces a clear error on\n * unsupported expressions rather than silently producing wrong output.\n */\nexport function buildVtlInput(\n body: string,\n headers: Record<string, string>,\n querystring: Record<string, string>,\n pathParams: Record<string, string>\n): VtlInput {\n let jsonBodyCache: unknown;\n let jsonBodyParsed = false;\n let jsonBodyParseError = false;\n function lazyJson(opts?: { throwOnParseError?: boolean }): unknown {\n if (!jsonBodyParsed) {\n jsonBodyParsed = true;\n if (body.length === 0) {\n jsonBodyCache = null;\n } else {\n try {\n jsonBodyCache = JSON.parse(body);\n } catch {\n jsonBodyCache = null;\n jsonBodyParseError = true;\n }\n }\n }\n if (jsonBodyParseError && opts?.throwOnParseError) {\n // Issue (#507) item 7: `$input.json(...)` against a non-JSON body\n // surfaces a `VtlEvaluationError` (AWS API Gateway returns\n // 400 + `{\"message\": \"Invalid JSON request body\"}` here; cdk-local's\n // dispatcher catches the error and returns 502 + the template + reason\n // in the body, which is the closest local approximation given that\n // the existing dispatch path treats every VtlEvaluationError as a\n // template-failure 502 — see `vtlFailure` in\n // `rest-v1-integrations.ts`). `$input.path(...)` keeps the pre-fix\n // null-on-parse-failure behavior because it returns a native value\n // and consumers (e.g. `#if(!$input.path('$.x'))`) often expect that\n // shape; `$input.json(...)` is the documented entry point for\n // JSON-stringified slices and AWS only fails THIS surface.\n throw new VtlEvaluationError(\n '$input.json(...) cannot be evaluated: request body is not valid JSON. ' +\n 'On AWS API Gateway this surface returns HTTP 400 with {\"message\":\"Invalid JSON request body\"}; ' +\n 'use $input.body or $input.path(...) when the upstream may emit non-JSON content.'\n );\n }\n return jsonBodyCache;\n }\n // `$input.json('$.path')` — JSON-stringified. Throws VtlEvaluationError\n // when the body is non-empty and not valid JSON (Issue (#507) item 7).\n function jsonFn(...args: unknown[]): string {\n const expr = args.length > 0 ? String(args[0]) : '$';\n const val = applyJsonPath(lazyJson({ throwOnParseError: true }), expr);\n return JSON.stringify(val ?? null);\n }\n // `$input.path('$.path')` — native value. Returns `null` when the body\n // is not valid JSON (matches AWS's lenient native-path semantics).\n function pathFn(...args: unknown[]): unknown {\n const expr = args.length > 0 ? String(args[0]) : '$';\n return applyJsonPath(lazyJson(), expr);\n }\n // `$input.params()` / `$input.params('name')`.\n function paramsFn(...args: unknown[]): unknown {\n if (args.length === 0) {\n return { header: headers, querystring, path: pathParams };\n }\n const arg = String(args[0]);\n if (arg === 'header') return headers;\n if (arg === 'querystring') return querystring;\n if (arg === 'path') return pathParams;\n // params('<name>') — AWS precedence: path, querystring, header.\n if (Object.prototype.hasOwnProperty.call(pathParams, arg)) return pathParams[arg];\n if (Object.prototype.hasOwnProperty.call(querystring, arg)) return querystring[arg];\n if (Object.prototype.hasOwnProperty.call(headers, arg)) return headers[arg];\n // Case-insensitive header lookup as a last resort.\n const lowerArg = arg.toLowerCase();\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === lowerArg) return v;\n }\n return null;\n }\n return {\n body,\n get jsonBody() {\n return lazyJson();\n },\n headers,\n querystring,\n path: pathParams,\n // Method-call surface — Velocity treats these as zero-arg property\n // accessors when no parens are used. We expose them as functions so\n // `$input.json('$')` works; `$input.body` is a plain string already.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ...({ json: jsonFn, path: pathFn, params: paramsFn } as any),\n } as VtlInput;\n}\n\n/**\n * Minimal JSONPath evaluator. Supports `$`, `$.field`, `$.field.sub`,\n * `$.array[index]`. Unsupported syntax throws so the user sees a clear\n * pointer to the gap.\n */\nexport function applyJsonPath(root: unknown, expr: string): unknown {\n const trimmed = expr.trim();\n if (trimmed === '$' || trimmed.length === 0) return root;\n if (!trimmed.startsWith('$')) {\n throw new VtlEvaluationError(`JSONPath must start with '$': got '${trimmed}'`);\n }\n let cursor: unknown = root;\n let i = 1;\n while (i < trimmed.length) {\n const c = trimmed[i]!;\n if (c === '.') {\n i++;\n // Property access: `.field`.\n const m = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(trimmed.slice(i));\n if (!m) {\n throw new VtlEvaluationError(\n `Unsupported JSONPath syntax at position ${i}: '${trimmed}' (${getEmbedConfig().productName} supports $, $.field, $.field.sub, $.array[index] only).`\n );\n }\n cursor = lookupField(cursor, m[0]);\n i += m[0].length;\n continue;\n }\n if (c === '[') {\n const close = trimmed.indexOf(']', i);\n if (close === -1) {\n throw new VtlEvaluationError(`Unterminated [ in JSONPath: '${trimmed}'`);\n }\n const inside = trimmed.slice(i + 1, close).trim();\n if (/^-?\\d+$/.test(inside)) {\n const idx = Number(inside);\n if (Array.isArray(cursor)) {\n cursor = (cursor as unknown[])[idx];\n } else {\n cursor = null;\n }\n } else if (\n (inside.startsWith('\"') && inside.endsWith('\"')) ||\n (inside.startsWith(\"'\") && inside.endsWith(\"'\"))\n ) {\n cursor = lookupField(cursor, inside.slice(1, -1));\n } else {\n throw new VtlEvaluationError(\n `Unsupported JSONPath bracket expression: '${inside}' (${getEmbedConfig().productName} supports integer indices and quoted string keys only).`\n );\n }\n i = close + 1;\n continue;\n }\n throw new VtlEvaluationError(`Unexpected character in JSONPath at position ${i}: '${trimmed}'`);\n }\n return cursor;\n}\n\n/**\n * Build a `$context` binding from a request snapshot + matched route.\n * The mapping mirrors what AWS API Gateway exposes (see AWS docs:\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html).\n */\nexport function buildVtlRequestContext(args: {\n requestId: string;\n httpMethod: string;\n resourcePath: string;\n stage: string;\n sourceIp: string;\n userAgent: string;\n}): VtlRequestContext {\n return {\n requestId: args.requestId,\n httpMethod: args.httpMethod,\n resourcePath: args.resourcePath,\n stage: args.stage,\n identity: {\n sourceIp: args.sourceIp,\n userAgent: args.userAgent,\n },\n };\n}\n","/**\n * `IntegrationResponses[]` selection logic for `cdkl start-api`'s\n * REST v1 non-AWS_PROXY integrations (#457).\n *\n * AWS API Gateway picks one `IntegrationResponses[]` entry per call to\n * shape the HTTP response. The rule (from AWS docs):\n *\n * - AWS matches `SelectionPattern` (a regex) against a per-integration\n * match target REGARDLESS of whether the backend returned success\n * or error. The first regex-matching entry wins. The match target\n * depends on the integration type:\n * - Lambda: `errorMessage` field of the parsed return value, or\n * the sentinel `'success'` when the payload has no errorMessage.\n * - HTTP / HTTP_PROXY: the HTTP status code as a string\n * (e.g. `'404'` / `'200'`).\n * Falls back to the default entry (`SelectionPattern` undefined or\n * `''`) when no regex matches. If no default exists either, the\n * caller's `fallbackStatusCode` drives the response (200 / 500\n * depending on outcome).\n *\n * Notes\n * -----\n *\n * - AWS pre-compiles `SelectionPattern` as a regex with `^pattern$`\n * anchoring. cdk-local matches that — `'.*Not Found.*'` works against\n * errorMessage values containing \"Not Found\", but a bare\n * `'Not Found'` only matches the literal string.\n * - The `StatusCode` field on the selected entry drives the HTTP\n * response status. AWS stores it as a string; cdk-local parses to int.\n * - `IntegrationResponses` is OPTIONAL on AWS — when absent for a non-\n * AWS_PROXY integration, AWS returns the backend response as-is\n * (with `application/json` for Lambda success). cdk-local mirrors this.\n * - PR #505 review fix 14: an earlier draft short-circuited the\n * success branch to the default entry without running the regex\n * loop, which silently dropped success-side selection (e.g. a\n * `SelectionPattern: '200'` entry never matched). The current\n * implementation always runs the loop.\n */\n\nimport { VtlEvaluationError } from './vtl-engine.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Shape of one entry in `Integration.IntegrationResponses`. CFn property\n * names (PascalCase).\n */\nexport interface IntegrationResponseEntry {\n /** HTTP status code AWS returns when this entry is selected. */\n StatusCode: string;\n /**\n * Regex pattern AWS matches against the integration's error / status.\n * Undefined / empty marks this as the \"default\" entry. AWS allows ONE\n * default entry per integration; multiple defaults is a template error\n * but cdk-local just picks the first one for resilience.\n */\n SelectionPattern?: string;\n /**\n * Response header literals — values can be:\n * - `\"'literal'\"` (single-quoted; AWS unwraps quotes).\n * - `\"integration.response.body.path\"` (mapped from backend body).\n * - `\"integration.response.header.<name>\"` (mapped from backend header).\n * - `\"context.<field>\"` (mapped from `$context`).\n *\n * cdkl v1 supports the single-quoted literal form. Mapping expressions\n * surface a warn and are skipped (rather than silently producing an\n * empty header — AWS gives a defined-but-unhelpful header on local\n * mismatches, which we don't want to mimic).\n */\n ResponseParameters?: Record<string, string>;\n /**\n * VTL templates keyed by content-type. The template evaluates against\n * a context where `$input.body` is the backend response body and\n * `$inputRoot` is the parsed JSON (Lambda's native return value).\n * AWS picks the content-type per Accept header; cdk-local picks the\n * `application/json` entry first, then any other entry.\n */\n ResponseTemplates?: Record<string, string>;\n /** `CONVERT_TO_TEXT` / `CONVERT_TO_BINARY` — cdk-local treats both as text. */\n ContentHandling?: string;\n}\n\n/**\n * Result of selecting one entry. Surfaced to the dispatchers so they\n * apply ResponseTemplates / ResponseParameters consistently.\n */\nexport interface SelectedIntegrationResponse {\n /** The picked entry. `null` when no entry matches (caller default). */\n entry: IntegrationResponseEntry | null;\n /** Parsed status code, defaulting to 200 when entry is null / unparseable. */\n statusCode: number;\n}\n\n/**\n * Pick the right `IntegrationResponses[]` entry for the given outcome.\n *\n * Per AWS docs, `SelectionPattern` is matched against the backend\n * outcome regardless of whether the backend returned success or error —\n * a `SelectionPattern: '200'` entry IS expected to match an HTTP 200\n * upstream response. cdk-local ALWAYS runs the regex loop first and only\n * falls to the default entry when no pattern matches; pre-#505-review\n * the success branch short-circuited to the default entry without\n * running the regex loop, which silently dropped success-side selection.\n *\n * @param entries - The `IntegrationResponses[]` array from the template\n * (already extracted from the route's Integration property).\n * @param matchTarget - The string AWS would match `SelectionPattern`\n * against. For HTTP / HTTP_PROXY this is `String(upstream.status)`;\n * for Lambda this is the `errorMessage` field on the parsed payload,\n * or the sentinel `'success'` when the payload has no `errorMessage`.\n * For MOCK this is unused (MOCK dispatch picks by `StatusCode`).\n * @param fallbackStatusCode - Status code to use when `entries` is empty\n * or no entry matches AND no default entry exists. HTTP / HTTP_PROXY\n * pass the upstream status; Lambda passes 200 on success / 500 on\n * error.\n */\nexport function selectIntegrationResponse(\n entries: IntegrationResponseEntry[] | undefined,\n matchTarget: string,\n fallbackStatusCode = 200\n): SelectedIntegrationResponse {\n if (!entries || entries.length === 0) {\n return { entry: null, statusCode: fallbackStatusCode };\n }\n // Locate the default entry (empty / missing SelectionPattern). Used\n // when no SelectionPattern matches the target.\n const defaultEntry = entries.find(\n (e) => e.SelectionPattern === undefined || e.SelectionPattern === ''\n );\n\n // Walk every entry that DOES have a SelectionPattern. AWS anchors the\n // regex with `^...$` and uses case-sensitive matching by default — we\n // mirror it. This runs unconditionally (regardless of upstream\n // success / error) per AWS's documented behavior.\n for (const entry of entries) {\n if (entry.SelectionPattern === undefined || entry.SelectionPattern === '') continue;\n try {\n const re = new RegExp(`^${entry.SelectionPattern}$`);\n if (re.test(matchTarget)) {\n return { entry, statusCode: parseStatus(entry.StatusCode, fallbackStatusCode) };\n }\n } catch {\n // Invalid regex in template — skip the entry but don't abort the\n // whole dispatch. AWS rejects invalid regex at template-validation\n // time; cdk-local is more forgiving here so a typo doesn't make the\n // whole route un-emulatable.\n }\n }\n\n // Fall back to default entry on no-match (matches AWS docs).\n const entry = defaultEntry ?? null;\n return {\n entry,\n statusCode:\n entry !== null ? parseStatus(entry.StatusCode, fallbackStatusCode) : fallbackStatusCode,\n };\n}\n\n/**\n * Parse an `IntegrationResponse.StatusCode` value into an HTTP status\n * code, returning `undefined` when the value is malformed or outside\n * the valid `[100, 600)` range.\n *\n * Issue (#507) items 6 + 7 + PR #515 item 4: this is the single source\n * of truth for \"is `raw` a usable HTTP status code?\" — `parseStatus`\n * in `rest-v1-integrations.ts` and this module's `parseStatus(raw, fallback)`\n * both delegate here so future shape-tightening (e.g. accepting `0x10`\n * the way `Number(\"0x10\") === 16` does) lands in one place.\n *\n * Validation rules:\n * - non-string non-number → undefined\n * - empty / whitespace-only string → undefined\n * - NaN / non-integer → undefined\n * - integer outside `[100, 600)` (HTTP valid range) → undefined\n * - hex literal `\"0x10\"` → Number coerces to 16 but below 100 → undefined\n */\nexport function tryParseStatus(raw: unknown): number | undefined {\n if (typeof raw === 'number') {\n if (Number.isInteger(raw) && raw >= 100 && raw < 600) return raw;\n return undefined;\n }\n if (typeof raw !== 'string') return undefined;\n const trimmed = raw.trim();\n if (trimmed === '') return undefined;\n const parsed = Number(trimmed);\n if (!Number.isInteger(parsed)) return undefined;\n if (parsed < 100 || parsed >= 600) return undefined;\n return parsed;\n}\n\nfunction parseStatus(raw: unknown, fallback: number): number {\n return tryParseStatus(raw) ?? fallback;\n}\n\n/**\n * Evaluate `IntegrationResponse.ResponseParameters` — header literals\n * mapped onto the HTTP response. Returns `{name: value}` for every entry\n * we could resolve; unresolvable entries (non-literal / mapping\n * expression) get a warning via `onUnsupported` and are skipped.\n *\n * AWS format: keys are `method.response.header.<HeaderName>`; values\n * are `'literal'` (with single quotes) or mapping expressions\n * (`integration.response.body.X` / `integration.response.header.X` /\n * `context.X`). cdkl v1 supports the literal form only.\n *\n * PR #511 review fix-back: header names are lowercased here so the\n * returned map shares the same key namespace as the dispatcher's\n * default-initialized headers (`{'content-type': '...'}`). Without\n * normalization a template that sets `Content-Type` PascalCase via\n * ResponseParameters produced a headers object carrying BOTH\n * `'content-type': 'application/json'` (default) AND\n * `'Content-Type': 'text/xml'` (overlay), which downstream HTTP\n * serialization rendered as two conflicting headers — AWS-deployed\n * only ever returns one. By lowercasing every key, overlays simply\n * overwrite the default-initializer entry like AWS does.\n */\nexport function evaluateResponseParameters(\n responseParameters: Record<string, string> | undefined,\n opts: { onUnsupported?: (key: string, value: string, reason: string) => void } = {}\n): Record<string, string> {\n if (!responseParameters) return {};\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(responseParameters)) {\n const headerMatch = /^method\\.response\\.header\\.(.+)$/.exec(key);\n if (!headerMatch) {\n opts.onUnsupported?.(\n key,\n value,\n `Only method.response.header.<name> keys are supported on REST v1 ResponseParameters; ${getEmbedConfig().productName} cannot map ${key}.`\n );\n continue;\n }\n const headerName = headerMatch[1]!.toLowerCase();\n if (typeof value !== 'string') {\n opts.onUnsupported?.(key, String(value), `non-string ResponseParameter value`);\n continue;\n }\n if (value.length >= 2 && value.startsWith(\"'\") && value.endsWith(\"'\")) {\n out[headerName] = value.slice(1, -1);\n continue;\n }\n // Mapping expression — log + skip. AWS docs:\n // https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html\n opts.onUnsupported?.(\n key,\n value,\n `ResponseParameter value '${value}' is a mapping expression (integration.response.* / context.*) which ${getEmbedConfig().cliName} start-api does not emulate. Only single-quoted literals are honored.`\n );\n }\n return out;\n}\n\n/**\n * Pick the response template AWS would render for the given Accept\n * header. AWS uses content negotiation; cdk-local picks `application/json`\n * first, then any other entry. Returns `undefined` when no template is\n * configured (caller emits the backend body verbatim).\n *\n * The chosen template's content-type is also returned so the dispatcher\n * can emit a matching `Content-Type` header (matches AWS-deployed\n * behavior).\n */\nexport function pickResponseTemplate(\n responseTemplates: Record<string, string> | undefined,\n accept: string | undefined\n): { template: string; contentType: string } | undefined {\n if (!responseTemplates) return undefined;\n const entries = Object.entries(responseTemplates);\n if (entries.length === 0) return undefined;\n\n if (accept) {\n // Match the FIRST accept-type that has a template. We split on `,`\n // and ignore quality values for simplicity.\n const acceptTypes = accept\n .split(',')\n .map((s) => s.split(';')[0]!.trim())\n .filter(Boolean);\n for (const acceptType of acceptTypes) {\n for (const [ct, template] of entries) {\n if (ct === acceptType) return { template, contentType: ct };\n }\n }\n }\n\n // Default: application/json > anything else.\n const jsonEntry = responseTemplates['application/json'];\n if (jsonEntry !== undefined) return { template: jsonEntry, contentType: 'application/json' };\n const first = entries[0]!;\n return { template: first[1], contentType: first[0] };\n}\n\n/**\n * Re-export VtlEvaluationError so callers don't have to import from two\n * modules.\n */\nexport { VtlEvaluationError };\n","/**\n * REST v1 non-AWS_PROXY integration dispatchers for `cdkl start-api`\n * (#457).\n *\n * Five integration kinds are supported:\n *\n * - **`AWS_PROXY`** — handled by the existing AWS_PROXY path in\n * `http-server.ts`. NOT a concern of this module.\n * - **`AWS`** (Lambda non-proxy with VTL): apply\n * `Integration.RequestTemplates['<content-type>']` (VTL) to the\n * request body to produce the Lambda event payload, invoke the\n * Lambda via RIE, apply\n * `IntegrationResponses[N].ResponseTemplates['<content-type>']` to\n * the Lambda return value. `IntegrationResponses[N].SelectionPattern`\n * drives status-code selection (regex against Lambda's\n * `errorMessage` field).\n * - **`HTTP_PROXY`** — forward the HTTP request to `Integration.Uri`\n * mostly verbatim. `Integration.RequestParameters` (header / path /\n * query rewrites) apply. No VTL.\n * - **`HTTP`** (non-proxy) — same as HTTP_PROXY but VTL templates\n * transform request + response.\n * - **`MOCK`** (non-CORS-preflight subset) — evaluate\n * `IntegrationResponses[N].ResponseTemplates['application/json']` as\n * VTL against an empty input. Status code from\n * `IntegrationResponses[N].StatusCode`.\n *\n * Each dispatcher returns a `RestV1IntegrationOutcome` the http-server\n * pipes onto the `ServerResponse`. The dispatchers themselves are\n * pure-functional (no `ServerResponse` mutation) so they're easy to\n * unit-test.\n *\n * Limitations\n * -----------\n *\n * - VTL features outside the supported subset (see\n * `src/local/vtl-engine.ts` for the full list) surface a clear error\n * via `VtlEvaluationError`; cdk-local's dispatcher catches it and emits a\n * `502 Bad Gateway` with the template + error name in the body. AWS\n * would surface a similar 5xx for VTL evaluation failures.\n * - `Integration.RequestParameters` mapping expressions\n * (`integration.request.header.X = method.request.header.Y`) are\n * implemented for direction = REQUEST. The reverse direction is\n * handled by `evaluateResponseParameters` in\n * `integration-response-selector.ts`.\n * - The HTTP_PROXY / HTTP dispatchers do NOT verify TLS certificates\n * against the system CA store — uses Node's default fetch behavior.\n * This is acceptable for local-dev (the upstream URL is the user's\n * own).\n * - **SSRF surface**: `Integration.Uri` is accepted verbatim and\n * passed to `fetch()`; cdk-local does NOT block private / loopback /\n * link-local destinations. A `warnSsrfRiskyUri()` helper surfaces a\n * warn at server boot when an Uri's hostname resolves to a\n * well-known internal address (IMDS, loopback, link-local, RFC1918)\n * so users see the risk in their integ logs. Threading an\n * `--allow-internal-uri` block flag is deferred — this is a\n * developer-loop tool, not a security boundary; warn-and-proceed\n * matches the precedent set by the cognito JWKS pass-through\n * fallback. The source URI is the user's own CDK template, so the\n * attack surface requires a malicious cdk.out or templated value.\n */\n\nimport type { ContainerPool } from './container-pool.js';\nimport { invokeRie } from './rie-client.js';\nimport {\n evaluateResponseParameters,\n pickResponseTemplate,\n selectIntegrationResponse,\n tryParseStatus,\n type IntegrationResponseEntry,\n} from './integration-response-selector.js';\nimport {\n buildDefaultUtil,\n buildVtlInput,\n buildVtlRequestContext,\n evaluateVtl,\n VtlEvaluationError,\n type VtlContext,\n} from './vtl-engine.js';\nimport { getLogger } from '../utils/logger.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Per-request snapshot the dispatchers consume. Mirrors the shape\n * `http-server.handleRequest` already builds for AWS_PROXY — keeping\n * this aligned simplifies the dispatch wiring.\n */\nexport interface RestV1IntegrationRequest {\n /** HTTP method (uppercase). */\n method: string;\n /** Request path AFTER matching (relative to API root). */\n matchedPath: string;\n /** Path parameters from {param} placeholders. */\n pathParameters: Record<string, string>;\n /** Query-string single-value form (last wins on duplicates). */\n querystring: Record<string, string>;\n /** Header single-value form (lowercased, comma-joined dupes). */\n headers: Record<string, string>;\n /** Raw request body as a Buffer. */\n body: Buffer;\n /** Client source IP (for `$context.identity.sourceIp`). */\n sourceIp: string;\n /** User agent header (for `$context.identity.userAgent`). */\n userAgent: string;\n /** Route's stage name (for `$context.stage`). */\n stage: string;\n /** Route's path pattern (for `$context.resourcePath`). */\n resourcePath: string;\n /** Synthesized request ID (UUID-shape, opaque). */\n requestId: string;\n}\n\n/**\n * What the dispatchers return — the http-server applies this to the\n * outgoing `ServerResponse`.\n */\nexport interface RestV1IntegrationOutcome {\n /** Final HTTP status code. */\n statusCode: number;\n /** Response headers (lowercase keys; case is preserved on output). */\n headers: Record<string, string>;\n /** Response body. May be empty. */\n body: string | Buffer;\n}\n\n/**\n * Configuration passed by the http-server to every dispatcher — the\n * shared services (container pool, timeouts) live here so the\n * dispatchers themselves take only request data.\n */\nexport interface RestV1DispatcherDeps {\n /**\n * Used by AWS Lambda non-proxy to acquire a warm RIE container. The\n * dispatcher only invokes `acquire` + `release`; the narrower\n * `Pick<ContainerPool, ...>` shape lets unit tests pass a minimal\n * mock without an `as any` cast (PR #515 item 5).\n */\n pool: Pick<ContainerPool, 'acquire' | 'release'>;\n /** RIE invoke timeout in ms. */\n rieTimeoutMs: number;\n /**\n * HTTP fetch override — Node's `fetch` by default; overridable for\n * unit tests against in-memory mock servers.\n */\n fetch?: typeof globalThis.fetch;\n}\n\n// ==================== MOCK integration ===================================\n\n/**\n * MOCK integration body parsed at boot time. Only stores what the\n * dispatcher needs — the discovery layer is the single source for\n * `Integration.IntegrationResponses[]` translation.\n */\nexport interface MockIntegrationConfig {\n kind: 'mock';\n /**\n * `Integration.RequestTemplates['application/json']` (or first\n * available content type). MOCK uses the request template to drive\n * status-code selection — AWS reads `{\"statusCode\": <N>}` from the\n * rendered template and matches against `IntegrationResponses[].StatusCode`.\n */\n requestTemplate: string | undefined;\n /** Selected from `Integration.IntegrationResponses[]`. */\n responses: IntegrationResponseEntry[];\n}\n\n/**\n * Dispatch a MOCK integration. AWS MOCK semantics:\n *\n * 1. Render `RequestTemplates['application/json']` (VTL) against the\n * request — yields a JSON object like `{\"statusCode\": 200}`.\n * 2. Parse the rendered JSON; pick the `IntegrationResponses[]` entry\n * whose `StatusCode` equals the parsed `statusCode` (string compare,\n * mirroring AWS).\n * 3. Render the picked entry's `ResponseTemplates[<content-type>]`\n * against an empty body context and emit it.\n * 4. Apply `ResponseParameters` header literals.\n *\n * When no request template is configured AWS defaults to picking the\n * `IntegrationResponses[]` entry with `SelectionPattern === ''` (or the\n * first entry).\n */\nexport function dispatchMockIntegration(\n config: MockIntegrationConfig,\n req: RestV1IntegrationRequest\n): RestV1IntegrationOutcome {\n const logger = getLogger().child('start-api');\n\n const ctx = buildVtlContextFromRequest(req, '');\n let pickedStatus: number | undefined;\n if (config.requestTemplate !== undefined && config.requestTemplate.trim().length > 0) {\n try {\n const rendered = evaluateVtl(config.requestTemplate, ctx);\n pickedStatus = extractStatusCodeFromRendered(rendered);\n } catch (err) {\n return vtlFailure('request', err, config.requestTemplate);\n }\n }\n\n // Find the matching response entry.\n let entry: IntegrationResponseEntry | null = null;\n if (pickedStatus !== undefined) {\n entry =\n config.responses.find((e) => tryParseStatus(e.StatusCode) === pickedStatus) ??\n defaultResponseEntry(config.responses);\n } else {\n entry = defaultResponseEntry(config.responses);\n }\n if (!entry) {\n return {\n statusCode: pickedStatus ?? 200,\n headers: { 'content-type': 'application/json' },\n body: '',\n };\n }\n\n const accept = req.headers['accept'];\n const picked = pickResponseTemplate(entry.ResponseTemplates, accept);\n // Build response context with `$inputRoot = null` (MOCK has no\n // backend body to feed into $inputRoot).\n const respCtx = buildVtlContextFromRequest(req, '', null);\n let body = '';\n let contentType = 'application/json';\n if (picked) {\n try {\n body = evaluateVtl(picked.template, respCtx);\n } catch (err) {\n return vtlFailure('response', err, picked.template);\n }\n contentType = picked.contentType;\n }\n\n const headers: Record<string, string> = { 'content-type': contentType };\n Object.assign(\n headers,\n evaluateResponseParameters(entry.ResponseParameters, {\n onUnsupported: (_k, _v, reason) => logger.warn(`MOCK response: ${reason}`),\n })\n );\n // Issue (#507) item 4: AWS API Gateway omits Content-Type on empty MOCK\n // responses; mirror that here. ResponseParameters can still set\n // Content-Type if the template ships an explicit literal — that overlay\n // already happened above, so checking on `headers['content-type']` (not\n // `contentType`) preserves the user's explicit setting.\n if (body === '' && headers['content-type'] === contentType) {\n delete headers['content-type'];\n }\n\n return {\n statusCode: tryParseStatus(entry.StatusCode) ?? 200,\n headers,\n body,\n };\n}\n\n// ==================== HTTP_PROXY integration ============================\n\nexport interface HttpProxyIntegrationConfig {\n kind: 'http-proxy';\n /** Upstream URL (may contain `{path}` placeholders matching method PathPart). */\n uri: string;\n /** Override method (`Integration.IntegrationHttpMethod`). Defaults to request method when missing. */\n integrationHttpMethod?: string;\n /**\n * `Integration.RequestParameters` — keys like\n * `integration.request.header.X` / `.path.X` / `.querystring.X`,\n * values pointing at request data or single-quoted literals.\n */\n requestParameters?: Record<string, string>;\n /** `IntegrationResponses[]` — entries shape the response. */\n responses: IntegrationResponseEntry[];\n}\n\n/**\n * Dispatch an HTTP_PROXY integration. The request is forwarded verbatim\n * with `RequestParameters` mappings applied; the response is also\n * forwarded verbatim (AWS does NOT apply ResponseTemplates on HTTP_PROXY,\n * only IntegrationResponses[].SelectionPattern routes the status code).\n */\nexport async function dispatchHttpProxyIntegration(\n config: HttpProxyIntegrationConfig,\n req: RestV1IntegrationRequest,\n deps: RestV1DispatcherDeps\n): Promise<RestV1IntegrationOutcome> {\n const url = substituteUriPlaceholders(config.uri, req);\n const method = config.integrationHttpMethod ?? req.method;\n\n // Build request headers from the incoming request, then overlay\n // `RequestParameters`.\n const outHeaders: Record<string, string> = { ...req.headers };\n // Strip hop-by-hop / connection-specific headers.\n for (const drop of ['host', 'connection', 'content-length', 'transfer-encoding']) {\n delete outHeaders[drop];\n }\n applyRequestParameters(config.requestParameters, req, { headers: outHeaders });\n\n const fetchImpl = deps.fetch ?? globalThis.fetch;\n const fetchInit: RequestInit = { method, headers: outHeaders };\n // Forward the request body whenever the client sent one — DO NOT gate\n // on method. Reasons: (a) the integration may override the method via\n // `IntegrationHttpMethod`, so a client POST may map to an upstream\n // GET that still wants the body (matches AWS behavior); (b) some\n // upstreams accept bodies on non-canonical methods like DELETE.\n // `content-length` is already stripped at outHeaders setup; fetch\n // recomputes it from `body`.\n if (req.body.length > 0) {\n fetchInit.body = new Uint8Array(req.body);\n }\n let upstream: Response;\n try {\n upstream = await fetchImpl(url, fetchInit);\n } catch (err) {\n return {\n statusCode: 502,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n message: 'HTTP_PROXY upstream unreachable',\n url,\n reason: err instanceof Error ? err.message : String(err),\n }),\n };\n }\n\n // Read the body BEFORE building the headers map: `fetch` auto-decodes\n // gzip/deflate/br and removes `content-encoding` from `upstream.headers`,\n // so reading the body first ensures the resulting bytes are decoded\n // plaintext. We then strip `content-encoding` (and `content-length`)\n // from the forwarded headers regardless — if fetch already removed it\n // the strip is a no-op, but a Response constructed from raw bytes in\n // tests may still carry the original encoding tag.\n const upstreamBody = Buffer.from(await upstream.arrayBuffer());\n // IntegrationResponses[].SelectionPattern matches against the status\n // code as a string. ALWAYS run the regex loop (PR #505 fix 14) —\n // a `SelectionPattern: '200'` entry IS expected to match a 200\n // upstream response.\n const selected = selectIntegrationResponse(\n config.responses,\n String(upstream.status),\n upstream.status\n );\n\n // For HTTP_PROXY, AWS forwards the upstream response shape verbatim\n // when no entry is selected — only ResponseParameters / Status come\n // from the selected entry.\n const headers: Record<string, string> = {};\n upstream.headers.forEach((value, name) => {\n headers[name.toLowerCase()] = value;\n });\n // Strip `content-encoding` (fetch already decoded the body — the\n // header would mislead downstream clients into double-decoding) and\n // `content-length` (the post-decode byte count differs from the\n // upstream's encoded one). Both are case-insensitive lookups against\n // the lowercased map.\n delete headers['content-encoding'];\n delete headers['content-length'];\n const logger = getLogger().child('start-api');\n Object.assign(\n headers,\n evaluateResponseParameters(selected.entry?.ResponseParameters, {\n onUnsupported: (_k, _v, reason) => logger.warn(`HTTP_PROXY response: ${reason}`),\n })\n );\n\n return {\n statusCode: selected.entry ? selected.statusCode : upstream.status,\n headers,\n body: upstreamBody,\n };\n}\n\n// ==================== HTTP non-proxy integration ========================\n\nexport interface HttpIntegrationConfig {\n kind: 'http';\n uri: string;\n integrationHttpMethod?: string;\n requestParameters?: Record<string, string>;\n /** `Integration.RequestTemplates[<content-type>]` — VTL-transformed body. */\n requestTemplates?: Record<string, string>;\n responses: IntegrationResponseEntry[];\n}\n\n/**\n * Dispatch an HTTP (non-proxy) integration: HTTP_PROXY + VTL on both\n * directions. Same upstream-call shape; the request body is transformed\n * via VTL, and the response body is transformed via VTL too.\n */\nexport async function dispatchHttpIntegration(\n config: HttpIntegrationConfig,\n req: RestV1IntegrationRequest,\n deps: RestV1DispatcherDeps\n): Promise<RestV1IntegrationOutcome> {\n const logger = getLogger().child('start-api');\n const url = substituteUriPlaceholders(config.uri, req);\n const method = config.integrationHttpMethod ?? req.method;\n\n const ctx = buildVtlContextFromRequest(req, req.body.toString('utf-8'));\n const reqTemplate = pickRequestTemplate(config.requestTemplates, req.headers['content-type']);\n let outBody: string | undefined;\n let outContentType = req.headers['content-type'] ?? 'application/json';\n if (reqTemplate) {\n try {\n outBody = evaluateVtl(reqTemplate.template, ctx);\n } catch (err) {\n return vtlFailure('request', err, reqTemplate.template);\n }\n outContentType = reqTemplate.contentType;\n } else {\n outBody = req.body.toString('utf-8');\n }\n\n const outHeaders: Record<string, string> = { ...req.headers, 'content-type': outContentType };\n for (const drop of ['host', 'connection', 'content-length', 'transfer-encoding']) {\n delete outHeaders[drop];\n }\n applyRequestParameters(config.requestParameters, req, { headers: outHeaders });\n\n const fetchImpl = deps.fetch ?? globalThis.fetch;\n const fetchInit: RequestInit = { method, headers: outHeaders };\n // Forward the (possibly VTL-rewritten) body whenever it is non-empty\n // — DO NOT gate on method (see HTTP_PROXY rationale). `outBody` is a\n // VTL-template render output when the integration has a\n // `RequestTemplates` entry, otherwise the raw client body as UTF-8.\n if (outBody !== undefined && outBody.length > 0) {\n fetchInit.body = outBody;\n }\n let upstream: Response;\n try {\n upstream = await fetchImpl(url, fetchInit);\n } catch (err) {\n return {\n statusCode: 502,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n message: 'HTTP upstream unreachable',\n url,\n reason: err instanceof Error ? err.message : String(err),\n }),\n };\n }\n\n // Read the upstream body. Branch on the upstream's Content-Type:\n // text-like content types (`text/*`, `application/json`,\n // `application/xml`, `application/x-www-form-urlencoded`) are decoded\n // as UTF-8 so the VTL ResponseTemplates can run against the body text.\n // Other content types (binary blobs — images, octet-stream, etc.) go\n // straight through as a Buffer so cdk-local does not corrupt them via\n // UTF-8 decode. VTL templates that assume text against a binary\n // upstream are limited by this design — see the dispatcher docstring.\n const upstreamContentType = upstream.headers.get('content-type') ?? 'application/octet-stream';\n const isUpstreamTextLike = isTextLikeContentType(upstreamContentType);\n let upstreamText: string | undefined;\n let upstreamBinary: Buffer | undefined;\n if (isUpstreamTextLike) {\n upstreamText = await upstream.text();\n } else {\n upstreamBinary = Buffer.from(await upstream.arrayBuffer());\n }\n\n // SelectionPattern always runs against the status string (PR #505 fix 14).\n const selected = selectIntegrationResponse(\n config.responses,\n String(upstream.status),\n upstream.status\n );\n\n // Render response template against the upstream body. Only available\n // on the text-decoded path; binary upstreams that pick a\n // ResponseTemplates entry surface a warn-and-pass-through (no VTL run).\n let body: string | Buffer;\n let contentType = upstreamContentType;\n if (selected.entry) {\n const picked = pickResponseTemplate(selected.entry.ResponseTemplates, req.headers['accept']);\n if (picked) {\n if (upstreamText === undefined) {\n // Upstream is binary but a ResponseTemplate is configured. VTL\n // requires the body as a string; cdk-local cannot apply it without\n // corrupting binary content. Surface a warn and pass the binary\n // through as-is (matches the binary-pass-through fall-through\n // below).\n logger.warn(\n `HTTP response: ResponseTemplates set but upstream Content-Type ` +\n `'${upstreamContentType}' is binary; passing body through unchanged.`\n );\n body = upstreamBinary!;\n } else {\n const respCtx = buildVtlContextFromRequest(req, upstreamText, safeJsonParse(upstreamText));\n try {\n body = evaluateVtl(picked.template, respCtx);\n } catch (err) {\n return vtlFailure('response', err, picked.template);\n }\n contentType = picked.contentType;\n }\n } else {\n body = upstreamText ?? upstreamBinary!;\n }\n } else {\n body = upstreamText ?? upstreamBinary!;\n }\n\n const headers: Record<string, string> = { 'content-type': contentType };\n Object.assign(\n headers,\n evaluateResponseParameters(selected.entry?.ResponseParameters, {\n onUnsupported: (_k, _v, reason) => logger.warn(`HTTP response: ${reason}`),\n })\n );\n\n return {\n statusCode: selected.statusCode,\n headers,\n body,\n };\n}\n\n// ==================== AWS Lambda non-proxy integration ==================\n\nexport interface AwsLambdaIntegrationConfig {\n kind: 'aws-lambda';\n /** Logical id of the Lambda the integration invokes. */\n lambdaLogicalId: string;\n /** `Integration.RequestTemplates[<content-type>]` — VTL-transformed event. */\n requestTemplates?: Record<string, string>;\n /** `IntegrationResponses[]` — entries shape the HTTP response. */\n responses: IntegrationResponseEntry[];\n}\n\n/**\n * Dispatch an AWS (Lambda non-proxy) integration. The request body is\n * transformed via VTL into the Lambda event; the Lambda is invoked via\n * RIE; the return value is transformed via ResponseTemplates.\n *\n * AWS error routing: when the Lambda returns an object with an\n * `errorMessage` field (Node Lambda runtime convention), AWS treats it\n * as an error and matches `SelectionPattern` against the\n * `errorMessage`. Otherwise success.\n */\nexport async function dispatchAwsLambdaIntegration(\n config: AwsLambdaIntegrationConfig,\n req: RestV1IntegrationRequest,\n deps: RestV1DispatcherDeps\n): Promise<RestV1IntegrationOutcome> {\n const logger = getLogger().child('start-api');\n\n const ctx = buildVtlContextFromRequest(req, req.body.toString('utf-8'));\n const template = pickRequestTemplate(config.requestTemplates, req.headers['content-type']);\n let eventPayload: unknown;\n if (template) {\n let rendered: string;\n try {\n rendered = evaluateVtl(template.template, ctx);\n } catch (err) {\n return vtlFailure('request', err, template.template);\n }\n // The template's output is typically JSON; parse it. If the template\n // is just a literal string the Lambda receives the string verbatim\n // (matches AWS — the SDK does no JSON parsing on the event payload).\n try {\n eventPayload = JSON.parse(rendered);\n } catch {\n eventPayload = rendered;\n }\n } else {\n // No template → pass the raw body. AWS docs: \"When there is no\n // template, AWS passes through the body as-is.\"\n eventPayload = safeJsonParse(req.body.toString('utf-8')) ?? req.body.toString('utf-8');\n }\n\n // Acquire a warm RIE container for this Lambda.\n let handle;\n try {\n handle = await deps.pool.acquire(config.lambdaLogicalId);\n } catch (err) {\n return {\n statusCode: 502,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n message: 'Failed to acquire RIE container for AWS Lambda non-proxy integration',\n reason: err instanceof Error ? err.message : String(err),\n }),\n };\n }\n\n // Wrap every post-acquire path in `try { ... } finally { release }` so a\n // throw in the synchronous selectIntegrationResponse / pickResponseTemplate\n // / evaluateVtl block (very unlikely but possible on an exotic template)\n // never strands the warm container — matches the buffered AWS_PROXY path's\n // pattern in `http-server.ts` (Issue (#507) item 1).\n try {\n let invokeOutcome;\n try {\n invokeOutcome = await invokeRie(\n handle.containerHost,\n handle.hostPort,\n eventPayload,\n deps.rieTimeoutMs\n );\n } catch (err) {\n return {\n statusCode: 502,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n message: 'AWS Lambda non-proxy invocation failed',\n reason: err instanceof Error ? err.message : String(err),\n }),\n };\n }\n\n // Detect Lambda error envelope: `{ errorMessage, errorType?, stackTrace? }`.\n const payload = invokeOutcome.payload;\n const isError =\n payload !== null &&\n typeof payload === 'object' &&\n 'errorMessage' in (payload as Record<string, unknown>);\n const matchTarget = isError\n ? String((payload as Record<string, unknown>)['errorMessage'])\n : 'success';\n\n // Lambda success uses the sentinel match target `'success'` (which is\n // unlikely to match any user-written SelectionPattern, so it falls to\n // the default entry — preserves pre-fix #14 behavior on Lambda).\n // Lambda error matches against the errorMessage string per AWS docs.\n const selected = selectIntegrationResponse(config.responses, matchTarget, isError ? 500 : 200);\n\n // Build response context with $inputRoot = parsed Lambda payload (AWS\n // docs convention for response mapping templates).\n const respCtx = buildVtlContextFromRequest(req, JSON.stringify(payload ?? null), payload);\n\n let body = '';\n let contentType = 'application/json';\n if (selected.entry) {\n const picked = pickResponseTemplate(selected.entry.ResponseTemplates, req.headers['accept']);\n if (picked) {\n try {\n body = evaluateVtl(picked.template, respCtx);\n } catch (err) {\n return vtlFailure('response', err, picked.template);\n }\n contentType = picked.contentType;\n } else {\n // No template — AWS emits the payload as JSON (matches AWS docs).\n body = JSON.stringify(payload ?? null);\n }\n } else {\n body = JSON.stringify(payload ?? null);\n }\n\n const headers: Record<string, string> = { 'content-type': contentType };\n Object.assign(\n headers,\n evaluateResponseParameters(selected.entry?.ResponseParameters, {\n onUnsupported: (_k, _v, reason) => logger.warn(`AWS Lambda non-proxy response: ${reason}`),\n })\n );\n\n return {\n statusCode: selected.statusCode,\n headers,\n body,\n };\n } finally {\n deps.pool.release(handle);\n }\n}\n\n// ==================== Helpers ===========================================\n\nfunction buildVtlContextFromRequest(\n req: RestV1IntegrationRequest,\n body: string,\n inputRoot?: unknown\n): VtlContext {\n const input = buildVtlInput(body, req.headers, req.querystring, req.pathParameters);\n const context = buildVtlRequestContext({\n requestId: req.requestId,\n httpMethod: req.method,\n resourcePath: req.resourcePath,\n stage: req.stage,\n sourceIp: req.sourceIp,\n userAgent: req.userAgent,\n });\n return {\n input,\n context,\n util: buildDefaultUtil(),\n ...(inputRoot !== undefined && { inputRoot }),\n };\n}\n\nfunction pickRequestTemplate(\n requestTemplates: Record<string, string> | undefined,\n contentType: string | undefined\n): { template: string; contentType: string } | undefined {\n if (!requestTemplates) return undefined;\n const entries = Object.entries(requestTemplates);\n if (entries.length === 0) return undefined;\n if (contentType) {\n const primary = contentType.split(';')[0]!.trim();\n if (requestTemplates[primary] !== undefined) {\n return { template: requestTemplates[primary], contentType: primary };\n }\n }\n // AWS docs: \"If the Content-Type header is missing or doesn't match\n // any template, the application/json template is used.\"\n if (requestTemplates['application/json'] !== undefined) {\n return { template: requestTemplates['application/json'], contentType: 'application/json' };\n }\n const first = entries[0]!;\n return { template: first[1], contentType: first[0] };\n}\n\n/**\n * Extract `{\"statusCode\": <N>}` from a rendered MOCK request template.\n * AWS uses this single key to drive `IntegrationResponses[]` selection.\n *\n * Returns `undefined` when the rendered template is not JSON OR does not\n * carry a `{statusCode: N}` object OR the value is not a positive integer\n * (Issue (#507) item 6 — `Number.isInteger` rejects `\"200abc\"` /\n * fractional values that `Number.parseInt` would silently accept). On any\n * fallback case the caller falls back to the default `IntegrationResponses[]`\n * entry. The fallback path is logged at debug (Issue (#507) item 3) so\n * users diagnosing a MOCK dispatch see what AWS would have seen.\n */\nfunction extractStatusCodeFromRendered(rendered: string): number | undefined {\n const logFallback = (reason: string): undefined => {\n const truncated = rendered.length > 200 ? rendered.slice(0, 200) + '...' : rendered;\n getLogger()\n .child('start-api')\n .debug(\n `MOCK request template did not yield a statusCode selection driver (${reason}); falling back to the default IntegrationResponses[] entry. Rendered output: ${truncated}`\n );\n return undefined;\n };\n let parsed: unknown;\n try {\n parsed = JSON.parse(rendered);\n } catch {\n return logFallback('rendered output is not valid JSON');\n }\n if (!parsed || typeof parsed !== 'object' || !('statusCode' in parsed)) {\n return logFallback('rendered output has no statusCode field');\n }\n const val = (parsed as Record<string, unknown>)['statusCode'];\n // PR #511 review fix-back: tighten validation beyond `Number.isInteger`\n // so empty strings (Number(\"\") === 0), whitespace-only strings, negative\n // numbers, and out-of-range integers all reject. Valid HTTP status codes\n // live in [100, 599]; anything else falls back to the default entry.\n if (typeof val === 'number') {\n if (Number.isInteger(val) && val >= 100 && val < 600) return val;\n return logFallback(`statusCode ${val} is out of HTTP range [100, 600)`);\n }\n if (typeof val === 'string') {\n const trimmed = val.trim();\n if (trimmed === '') return logFallback('statusCode is empty / whitespace');\n const n = Number(trimmed);\n if (!Number.isInteger(n)) return logFallback(`statusCode '${val}' is not a valid integer`);\n if (n < 100 || n >= 600) return logFallback(`statusCode ${n} is out of HTTP range [100, 600)`);\n return n;\n }\n return logFallback(`statusCode has unexpected type '${typeof val}'`);\n}\n\nfunction defaultResponseEntry(\n entries: IntegrationResponseEntry[]\n): IntegrationResponseEntry | null {\n return entries.find((e) => e.SelectionPattern === undefined || e.SelectionPattern === '') ?? null;\n}\n\n/**\n * Heuristic: is the given HTTP `Content-Type` header value likely to\n * carry text content that VTL ResponseTemplates can safely render\n * against? Used by `dispatchHttpIntegration` to branch the upstream\n * body read between `.text()` (text-like) and `.arrayBuffer()` (binary\n * pass-through). Charset parameters are stripped before matching.\n *\n * Exported for unit testing.\n */\nexport function isTextLikeContentType(contentType: string): boolean {\n const primary = contentType.split(';')[0]!.trim().toLowerCase();\n if (primary.startsWith('text/')) return true;\n // Common text-shaped application types.\n if (\n primary === 'application/json' ||\n primary === 'application/xml' ||\n primary === 'application/x-www-form-urlencoded' ||\n primary === 'application/javascript' ||\n primary === 'application/ld+json'\n ) {\n return true;\n }\n // `application/*+json` / `application/*+xml` (e.g. `application/vnd.api+json`).\n if (\n primary.startsWith('application/') &&\n (primary.endsWith('+json') || primary.endsWith('+xml'))\n ) {\n return true;\n }\n return false;\n}\n\n/**\n * Classify a hostname or IP literal against well-known internal address\n * spaces. Used by `warnSsrfRiskyUri` at server boot to surface a warn\n * line per HTTP / HTTP_PROXY integration whose URI points at a\n * potentially-sensitive destination. Best-effort; does NOT do DNS\n * resolution — only matches hostname literals that are already an IP.\n *\n * Returns `undefined` when the host appears safe (public DNS name) OR\n * cannot be classified (DNS name that may resolve to an internal IP\n * the helper cannot see without async DNS).\n *\n * Exported for unit testing.\n */\nexport function classifyInternalHost(host: string): string | undefined {\n // Trim IPv6 brackets if present.\n const h = host.replace(/^\\[|\\]$/g, '');\n // AWS IMDS specifically (most actionable warning).\n if (h === '169.254.169.254' || h === '[fd00:ec2::254]' || h === 'fd00:ec2::254') {\n return 'AWS IMDS (169.254.169.254) — credentials exfiltration risk';\n }\n // IPv4 loopback (127.0.0.0/8).\n if (/^127\\.\\d+\\.\\d+\\.\\d+$/.test(h)) return 'IPv4 loopback (127.0.0.0/8)';\n // IPv6 loopback.\n if (h === '::1') return 'IPv6 loopback (::1)';\n // IPv4 link-local (169.254.0.0/16 — includes IMDS handled above).\n if (/^169\\.254\\.\\d+\\.\\d+$/.test(h)) return 'IPv4 link-local (169.254.0.0/16)';\n // IPv6 link-local (fe80::/10).\n if (/^fe[89ab][0-9a-f]?:/i.test(h)) return 'IPv6 link-local (fe80::/10)';\n // RFC1918 private ranges.\n if (/^10\\.\\d+\\.\\d+\\.\\d+$/.test(h)) return 'RFC1918 private (10.0.0.0/8)';\n if (/^192\\.168\\.\\d+\\.\\d+$/.test(h)) return 'RFC1918 private (192.168.0.0/16)';\n // 172.16.0.0/12 — 172.16-172.31.\n const m = /^172\\.(\\d+)\\.\\d+\\.\\d+$/.exec(h);\n if (m && Number(m[1]) >= 16 && Number(m[1]) <= 31) {\n return 'RFC1918 private (172.16.0.0/12)';\n }\n return undefined;\n}\n\n/**\n * Emit a `logger.warn` line for each HTTP / HTTP_PROXY integration\n * whose `Integration.Uri` parses to a hostname classified as internal\n * by `classifyInternalHost`. Called once at server boot from\n * `cdkl start-api`'s discovery pass; per-route deduplicated.\n *\n * cdk-local does NOT block the URI — this is a developer-loop tool, not a\n * security boundary, and warn-and-proceed matches the precedent set by\n * the cognito JWKS pass-through fallback. The right v2 follow-up is an\n * `--allow-internal-uri` flag (and an opposite default block) once the\n * surface is well-understood.\n */\nexport function warnSsrfRiskyUri(\n uri: string,\n routeLabel: string,\n warn: (msg: string) => void\n): void {\n let host: string;\n try {\n // Strip placeholders so URL() does not reject `{paramName}` shapes\n // — the substituted value at request time is what matters, but the\n // template Uri's literal host segment IS the right thing to\n // classify here.\n const sanitized = uri.replace(/\\{[^/{}]+\\}/g, 'x');\n host = new URL(sanitized).hostname;\n } catch {\n return; // Malformed Uri; route-discovery handles the error.\n }\n const classification = classifyInternalHost(host);\n if (classification !== undefined) {\n warn(\n `Integration URI for ${routeLabel} points at ${host} — ${classification}. ` +\n `${getEmbedConfig().productName} does NOT block this; ensure the upstream is intentional.`\n );\n }\n}\n\nfunction safeJsonParse(s: string): unknown {\n try {\n return JSON.parse(s);\n } catch {\n return null;\n }\n}\n\n/**\n * Apply `Integration.RequestParameters` mappings — header / query / path\n * rewrites that copy from `method.request.X` to `integration.request.Y`.\n *\n * Supported key shapes:\n * - `integration.request.header.<name>` → outgoing header\n * - `integration.request.querystring.<name>` → query string param (warn-and-skip)\n * - `integration.request.path.<name>` → path placeholder substitution (warn-and-skip)\n *\n * Supported value shapes (header case-insensitive + multi-value comma-joined,\n * querystring case-sensitive + last-wins; see {@link resolveRequestParameterValue}\n * and `vtl-engine.ts` `$input.params` for the matching read-side semantics):\n * - `method.request.header.<name>` → read incoming header\n * - `method.request.querystring.<name>` → read incoming query param\n * - `method.request.path.<name>` → read path parameter\n * - `'literal'` → single-quoted literal\n *\n * Unsupported mapping expressions are logged at warn and skipped (matches\n * the ResponseParameters handling in `integration-response-selector.ts`).\n *\n * Note: querystring / path-rewrite branches currently warn-and-skip; cdk-local\n * relies on `{paramName}` URI substitution for the canonical case (see\n * {@link substituteUriPlaceholders}). The previous `urlObj` parameter was\n * never used by the unimplemented querystring rewrite branch and has been\n * dropped (Issue (#507) item 2).\n */\nfunction applyRequestParameters(\n requestParameters: Record<string, string> | undefined,\n req: RestV1IntegrationRequest,\n out: { headers: Record<string, string> }\n): void {\n if (!requestParameters) return;\n const logger = getLogger().child('start-api');\n for (const [key, value] of Object.entries(requestParameters)) {\n const resolved = resolveRequestParameterValue(value, req);\n if (resolved === undefined) {\n logger.warn(\n `RequestParameter '${key}' value '${value}' is not a recognized mapping; skipping.`\n );\n continue;\n }\n const headerMatch = /^integration\\.request\\.header\\.(.+)$/.exec(key);\n const queryMatch = /^integration\\.request\\.querystring\\.(.+)$/.exec(key);\n const pathMatch = /^integration\\.request\\.path\\.(.+)$/.exec(key);\n if (headerMatch) {\n out.headers[headerMatch[1]!.toLowerCase()] = resolved;\n } else if (queryMatch) {\n // Querystring rewrites are recognized but cdk-local applies querystring\n // rewrites only via URI placeholder substitution; ignore.\n logger.warn(\n `RequestParameter '${key}' (querystring rewrite) is recognized but ${getEmbedConfig().productName} applies querystring rewrites only via URI placeholder substitution; ignoring.`\n );\n } else if (pathMatch) {\n // Path rewrites apply at URI substitution time. Log + skip — the\n // pre-substituted URI already used the path parameters via\n // `{paramName}` placeholders, so a separate rewrite is rarely needed.\n logger.warn(\n `RequestParameter '${key}' (path rewrite) is recognized but ${getEmbedConfig().productName} substitutes path placeholders via {param} in the URI; ignoring.`\n );\n } else {\n logger.warn(`Unsupported RequestParameter key '${key}'; skipping.`);\n }\n }\n}\n\n/**\n * Resolve a single `RequestParameters` value to a string.\n *\n * Case-sensitivity contract (Issue (#507) item 5; mirrored on the VTL\n * read side by `vtl-engine.ts` `$input.params`):\n *\n * - Header lookups are **case-insensitive** (the incoming-header map is\n * pre-lowercased by the http-server) and multi-value duplicates are\n * comma-joined.\n * - Querystring lookups are **case-sensitive** (matches AWS API Gateway's\n * deployed behavior) and multi-value duplicates surface only the\n * last-wins string (the http-server's request snapshot collapses\n * duplicates at parse time).\n * - Path parameters are case-sensitive (CFn template `{paramName}`\n * placeholders are case-sensitive by construction).\n */\nfunction resolveRequestParameterValue(\n raw: string,\n req: RestV1IntegrationRequest\n): string | undefined {\n if (raw.length >= 2 && raw.startsWith(\"'\") && raw.endsWith(\"'\")) {\n return raw.slice(1, -1);\n }\n const headerMatch = /^method\\.request\\.header\\.(.+)$/.exec(raw);\n if (headerMatch) return req.headers[headerMatch[1]!.toLowerCase()];\n const queryMatch = /^method\\.request\\.querystring\\.(.+)$/.exec(raw);\n if (queryMatch) return req.querystring[queryMatch[1]!];\n const pathMatch = /^method\\.request\\.path\\.(.+)$/.exec(raw);\n if (pathMatch) return req.pathParameters[pathMatch[1]!];\n return undefined;\n}\n\n/**\n * Substitute `{paramName}` placeholders in a URI string with the value\n * of the matching path parameter on the request. Used by HTTP_PROXY /\n * HTTP integrations whose `Integration.Uri` may contain such\n * placeholders (e.g. `https://upstream.example.com/users/{userId}`).\n */\nexport function substituteUriPlaceholders(uri: string, req: RestV1IntegrationRequest): string {\n return uri.replace(/\\{([^/{}]+)\\}/g, (_, name) => {\n const val = req.pathParameters[name];\n return val !== undefined ? encodeURIComponent(val) : '';\n });\n}\n\nfunction vtlFailure(\n direction: 'request' | 'response',\n err: unknown,\n template: string\n): RestV1IntegrationOutcome {\n const reason =\n err instanceof VtlEvaluationError\n ? err.message\n : err instanceof Error\n ? err.message\n : String(err);\n const body = JSON.stringify({\n message: `VTL ${direction}-template evaluation failed`,\n reason,\n template: template.length > 200 ? template.slice(0, 200) + '...' : template,\n });\n return {\n statusCode: 502,\n headers: { 'content-type': 'application/json' },\n body,\n };\n}\n","import { getLogger } from '../utils/logger.js';\nimport { pickFreePort, removeContainer, runDetached, streamLogs } from './docker-runner.js';\nimport type { ResolvedImageLambda, ResolvedZipLambda } from './lambda-resolver.js';\nimport { waitForRieReady } from './rie-client.js';\nimport { resolveRuntimeCodeMountPath, resolveRuntimeImage } from './runtime-image.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Per-Lambda warm container pool for `cdkl start-api` (D8.3).\n *\n * Two design forces:\n *\n * 1. **Concurrency**: a single Lambda RIE container serializes invokes\n * on its own. Modern HTTP API integrations / browser fanout makes\n * that immediately visible. Default pool size 2 (warm + 1 cold\n * backup); `--per-lambda-concurrency` raises to max 4.\n *\n * 2. **Resource budget**: idle containers cost RAM. After 60s of\n * inactivity an entry's idle handles are torn down — the next\n * request pays a fresh start cost.\n *\n * Implementation:\n *\n * - `Map<logicalId, ContainerPoolEntry>` keyed by Lambda logical ID.\n * - Per-entry `acquire()` / `release()` use a tiny mutex chain\n * (`growthMutex`) to serialize lazy growth. `acquire()` returns the\n * first idle handle; if all are in use and the pool is below the\n * cap, lazy-starts a new one; if all in use AND at the cap, the\n * waiter joins a `waitQueue` flushed by `release()`.\n *\n * - `dispose()` cancels every idle timer, removes every container, and\n * **tolerates per-container removal failures** — logged at warn,\n * loop continues. The verify.sh trap (`docker rm -f` over every\n * `cdkl-*` container) is the safety net.\n */\n\nexport interface ContainerHandle {\n logicalId: string;\n containerId: string;\n containerName: string;\n hostPort: number;\n containerHost: string;\n /** Stop the streaming-logs child process attached at boot. */\n stopLogStream: () => void;\n}\n\ninterface ContainerPoolEntry {\n logicalId: string;\n /** Currently idle handles ready to be `acquire()`d. */\n warm: ContainerHandle[];\n /** Currently in-use handles. */\n inUse: Set<ContainerHandle>;\n /**\n * Resolvers for `acquire()` calls that are blocked because every\n * handle is in use AND `pool.size === concurrencyCap`. Released by\n * the next `release()`. `dispose()` rejects every pending waiter\n * via the `reject` callback so the request handler returns 502\n * instead of hanging forever.\n */\n waitQueue: Array<{ resolve: (h: ContainerHandle) => void; reject: (err: Error) => void }>;\n /** 60s idle GC timer, reset on every `release()`. */\n idleTimer: NodeJS.Timeout | null;\n /** Serializes lazy growth so two concurrent `acquire()`s don't double-start. */\n growthMutex: Promise<void>;\n /**\n * Resolvers waiting for `inUse` to fully drain. Populated by\n * `dispose()` and resolved by `release()` whenever `inUse.size` hits\n * zero. Allows `dispose()` to await every in-flight handle before\n * tearing down — without this guard a request mid-`invokeRie` against\n * the pool would have its container killed (502 leak) AND a stale\n * `release(handle)` from its `finally` block would corrupt the\n * post-dispose entries map (the bug described in the PR review).\n */\n drainResolvers: Array<() => void>;\n}\n\n/**\n * Per-Lambda parameters used to spin up a container. Set once at server\n * boot — `acquire()` reads these from the pool's per-logical-id record.\n *\n * Discriminated union (closes #453): `kind === 'zip'` for traditional\n * ZIP-packaged Lambdas (Node.js / Python / Ruby / Java / etc. — base image\n * comes from `public.ecr.aws/lambda/<lang>:<v>`, code bind-mounted at\n * `/var/task` or `/var/runtime`); `kind === 'image'` for container Lambdas\n * (`Code.ImageUri` — image already includes the code, no bind mount,\n * `ImageConfig.Command` / `EntryPoint` / `WorkingDirectory` drive\n * invocation). The shared fields (`env`, `containerHost`, `debugPort`)\n * apply to both variants.\n */\nexport type ContainerSpec = ZipContainerSpec | ImageContainerSpec;\n\ninterface ContainerSpecBase {\n env: Record<string, string>;\n /**\n * Env keys whose VALUES are sensitive and must be kept off the\n * `docker run` argv (routed through `-e KEY`). Today: env keys that\n * resolved to a decrypted `SecureString` SSM parameter under\n * `--from-cfn-stack` (issue #99). Threaded verbatim into\n * `runDetached`'s `sensitiveEnvKeys`.\n */\n sensitiveEnvKeys?: ReadonlySet<string>;\n containerHost: string;\n /** Optional Node.js `--inspect-brk` port. */\n debugPort?: number;\n /**\n * Optional sized tmpfs mount for the warm container (issue #440 —\n * Lambda `Properties.EphemeralStorage.Size`). Resolved ONCE at server\n * boot from the function's template (same as `optDir` / `codeDir`)\n * and threaded into every cold-start of this Lambda's pool. Unset\n * when the template did not declare `EphemeralStorage` — the warm\n * container's `/tmp` is then whatever the base image provides\n * (preserves the pre-#440 behavior). Target path is `/tmp`. Applies\n * to BOTH ZIP and IMAGE Lambdas — Docker `--tmpfs` overlays inside\n * any container image just like on the public base images.\n */\n tmpfs?: { target: string; sizeMb: number };\n /**\n * Extra `--add-host` mappings forwarded to `docker run`. Used by\n * `cdkl start-api`'s WebSocket support (#462) to inject\n * `host.docker.internal:host-gateway` so Lambdas backing\n * WebSocket APIs can reach the host's `@connections` HTTP\n * endpoint when calling `apigatewaymanagementapi:PostToConnection`.\n * Resolved once at server boot (alongside `tmpfs` / `env`) and\n * threaded into every cold-start of this Lambda's pool. Empty\n * for Lambdas not backing a WebSocket API.\n */\n extraHosts?: { host: string; ip: string }[];\n /**\n * When `cdkl *` is invoked with `--profile <name>`, this is the host\n * path of a\n * synthesized AWS shared credentials file (one INI section, the\n * resolved `[<name>]` block). The pool bind-mounts it read-only at\n * the container path so SDK calls via `fromIni({ profile: '<name>' })`\n * inside the handler find their profile locally — production AWS\n * Lambda doesn't ship `~/.aws/`, so this is purely a local-development\n * convenience to keep handler code portable without source changes.\n *\n * Set in `local-start-api.ts` / `local-invoke.ts`'s startup flow\n * alongside the `AWS_SHARED_CREDENTIALS_FILE` + `AWS_PROFILE` env\n * entries (already in `env`) so the SDK's default chain + explicit\n * `fromIni({ profile })` both resolve to the same creds. Unset when\n * `--profile` was not passed (the env-var-only path stays in effect).\n */\n profileCredentialsFile?: { hostPath: string; containerPath: string };\n}\n\nexport interface ZipContainerSpec extends ContainerSpecBase {\n kind: 'zip';\n /**\n * The ZIP Lambda's resolved metadata. The pool reads `runtime` to pick\n * the base image and code mount path, `handler` for the container CMD,\n * and `logicalId` for the docker container name + log prefix.\n */\n lambda: ResolvedZipLambda;\n /**\n * Bind-mount source for the in-container deployment path. The target\n * path is `/var/task` for most runtimes and `/var/runtime` for the\n * `provided.al2` / `provided.al2023` OS-only runtimes — chosen by\n * `resolveRuntimeCodeMountPath(spec.lambda.runtime)` at acquire time.\n * Source is the asset dir or materialized inline tmpdir.\n */\n codeDir: string;\n /**\n * Pre-resolved bind-mount source for `/opt` (PR 6 of #224, issue\n * #232 — Lambda Layers). Resolved ONCE at server boot — for a\n * single-layer function this is the layer's asset dir; for multi-\n * layer functions this is a tmpdir that already merged the layers\n * in template order (later layers overwrite earlier files via\n * `cpSync({force: true})`). Undefined when the function declares\n * no layers. Why pre-resolve at the server level instead of per\n * cold-start: the merge is deterministic (templates are\n * static for the server's lifetime) and we want exactly ONE merged\n * dir to clean up at dispose.\n */\n optDir?: string;\n}\n\nexport interface ImageContainerSpec extends ContainerSpecBase {\n kind: 'image';\n /**\n * The container Lambda's resolved metadata. The pool reads `logicalId`\n * for the docker container name + log prefix. `runtime` / `handler`\n * are NOT set on the IMAGE branch (AWS contract: container Lambdas\n * don't have `Handler` — invocation is driven by `ImageConfig.Command`\n * or the image's own CMD; `Runtime` is also absent).\n */\n lambda: ResolvedImageLambda;\n /**\n * Pre-built local docker image tag / reference. Resolved ONCE at\n * server boot via `buildContainerImage` (local-build path against\n * `cdk.out` asset manifest) or `pullEcrImage` (ECR-pull fallback,\n * same-acct/region only). The pool passes this verbatim to `docker\n * run` — no further resolution happens on the per-cold-start path.\n *\n * On hot reload (`--watch`) the reload-orchestrator detects spec\n * signature changes via `reload-orchestrator.ts:specSignature`; a\n * change in `image` (e.g. the user edited the Dockerfile and the\n * deterministic tag flipped) triggers a pool teardown so the next\n * cold-start runs the newly-built image.\n */\n image: string;\n /**\n * `docker run --platform <linux/amd64|linux/arm64>` translated from\n * the Lambda's `Architectures` array. Threaded through to BOTH the\n * `docker build` (`buildContainerImage`) AND the `docker run` step\n * so an arm64 host running an x86_64 Lambda doesn't hit silent\n * emulation, and an x86_64 host running an arm64 Lambda doesn't\n * fail with `exec format error`.\n */\n platform: string;\n /**\n * `ImageConfig.Command` from the template. Empty array when the user\n * relies on the image's own CMD (the common case for `LAMBDA_TASK_ROOT`-\n * convention images). Forwarded as the CMD slot of `docker run`.\n */\n command: string[];\n /**\n * `ImageConfig.EntryPoint` from the template. Undefined when the user\n * relies on the image's default entrypoint (typically\n * `/lambda-entrypoint.sh` on AWS base images, which routes to RIE).\n * When set, the first entry maps to `docker run --entrypoint <first>`\n * and the rest are prepended to `cmd` as positional args — see\n * `docker-runner.ts:runDetached`.\n */\n entryPoint?: string[];\n /**\n * `ImageConfig.WorkingDirectory` → `docker run --workdir <dir>`.\n * Undefined when the image's own WORKDIR is sufficient.\n */\n workingDir?: string;\n}\n\nexport interface ContainerPoolOptions {\n /** Per-Lambda max concurrency (default 2, max 4). */\n perLambdaConcurrency: number;\n /** Whether to skip `docker pull`. The CLI's `--no-pull`. */\n skipPull?: boolean;\n /** Idle GC delay in ms. Defaults to 60_000; tests override via fake timers. */\n idleMs?: number;\n /** Whether to attach `docker logs -f` per container. Default true. */\n streamLogs?: boolean;\n}\n\nexport interface ContainerPool {\n /**\n * Acquire (or lazy-start) a warm container for the given Lambda. The\n * caller MUST eventually `release(handle)` — every code path through\n * the request handler runs `release` from a `finally`.\n */\n acquire(logicalId: string): Promise<ContainerHandle>;\n /** Mark a handle idle and reset its 60s idle GC timer. */\n release(handle: ContainerHandle): void;\n /** Tear down every container (warm + in-use). Tolerates removal failures. */\n dispose(): Promise<void>;\n}\n\nconst DEFAULT_IDLE_MS = 60_000;\nconst MAX_PER_LAMBDA_CONCURRENCY = 4;\nconst MIN_PER_LAMBDA_CONCURRENCY = 1;\n\n/**\n * Construct a ContainerPool. The `specs` map is keyed by logical ID; only\n * Lambdas in that map are reachable via `acquire()`. The pool starts\n * empty unless `prewarm: true` (a one-shot best-effort warm pass at\n * server boot — failures don't abort the server, they just mean the\n * first request to that Lambda pays cold-start cost).\n */\nexport function createContainerPool(\n specs: Map<string, ContainerSpec>,\n options: ContainerPoolOptions\n): ContainerPool {\n const logger = getLogger().child('container-pool');\n const concurrencyCap = clampConcurrency(options.perLambdaConcurrency);\n const idleMs = options.idleMs ?? DEFAULT_IDLE_MS;\n const streamingEnabled = options.streamLogs !== false;\n\n const entries = new Map<string, ContainerPoolEntry>();\n // Set once `dispose()` runs, so a stale `release(handle)` from a\n // request whose `finally` block raced the dispose teardown becomes a\n // no-op instead of corrupting the post-dispose entries map (entry\n // removed → release would push the handle into a freed `warm[]` and\n // re-arm the idle timer on a torn-down entry). The verify.sh\n // `docker rm -f cdkl-*` sweep is the safety net for the\n // not-yet-torn-down container itself.\n let disposed = false;\n\n /**\n * Tracks every in-flight `startOne` promise so `dispose()` can wait\n * for them (with a short timeout) and tear down the resulting\n * handles instead of leaking the container. Without this, a SIGINT\n * during a cold-start lands on an `acquire()` that's still inside\n * `runDetached` / `waitForRieReady`; when the start eventually\n * resolves, `entries.get(...)` is undefined and the handle is\n * dropped on the floor. Populated inside `startOne`'s entry path\n * (via `trackStart`); drained in `dispose()`.\n */\n const inFlightStarts = new Set<Promise<ContainerHandle>>();\n\n // Pre-create empty entries so `acquire()` never has to lazily build\n // the map under contention. Pool starts at size 0 per entry; growth\n // happens inside `acquire()` under the per-entry mutex.\n for (const logicalId of specs.keys()) {\n entries.set(logicalId, emptyEntry(logicalId));\n }\n\n function emptyEntry(logicalId: string): ContainerPoolEntry {\n return {\n logicalId,\n warm: [],\n inUse: new Set(),\n waitQueue: [],\n idleTimer: null,\n growthMutex: Promise.resolve(),\n drainResolvers: [],\n };\n }\n\n /**\n * Spin up one new container for the given Lambda spec. Returns a\n * handle the caller can write into the entry's data structures.\n *\n * Branches on `spec.kind`:\n * - `'zip'`: bind-mount the function's local code dir at\n * `/var/task` (or `/var/runtime` for `provided.*` runtimes),\n * base image from `public.ecr.aws/lambda/<lang>:<v>`, CMD =\n * `[<Handler>]`.\n * - `'image'`: no code bind-mount (image already includes the\n * code), base image is the pre-built local tag, CMD =\n * `ImageConfig.Command` (may be empty), optional EntryPoint /\n * WorkingDirectory / --platform applied verbatim.\n */\n async function startOne(spec: ContainerSpec): Promise<ContainerHandle> {\n const hostPort = await pickFreePort();\n const name = `${getEmbedConfig().resourceNamePrefix}-${spec.lambda.logicalId}-${process.pid}-${Math.floor(\n Math.random() * 1_000_000\n )}`;\n logger.debug(\n `Starting container ${name} for ${spec.lambda.logicalId} (kind=${spec.kind}) on ${spec.containerHost}:${hostPort}`\n );\n\n let containerId: string;\n if (spec.kind === 'zip') {\n // PR 6 (#232): one pre-resolved bind mount at `/opt` (when the\n // function declares any layers). Multi-layer merging happens in\n // `local-start-api.ts`'s `materializeLambdaLayers(...)` once at\n // server boot — Docker rejects two `-v ...:/opt:ro` entries at\n // the same target, so cdk-local can't rely on overlay layering and\n // must merge on the host instead (see ImagePlan.layersTmpDir\n // docstring in `cli/commands/local-invoke.ts`).\n const optMount = spec.optDir\n ? [{ hostPath: spec.optDir, containerPath: '/opt', readOnly: true }]\n : [];\n // Append the profile credentials file mount when --profile was\n // passed. Read-only — the container\n // has no business writing to its credentials file, and a writable\n // mount would let a compromised handler tamper with the host-side\n // temp file. Combined with `optMount` since Docker accepts an array\n // of -v args (no conflict with the /opt layer mount).\n const extraMounts = spec.profileCredentialsFile\n ? [\n ...optMount,\n {\n hostPath: spec.profileCredentialsFile.hostPath,\n containerPath: spec.profileCredentialsFile.containerPath,\n readOnly: true,\n },\n ]\n : optMount;\n // provided.al2 / provided.al2023 require the deployment package at\n // /var/runtime (where the base image's hardcoded entrypoint exec's\n // /var/runtime/bootstrap); every other runtime expects /var/task.\n const containerCodePath = resolveRuntimeCodeMountPath(spec.lambda.runtime);\n const image = resolveRuntimeImage(spec.lambda.runtime);\n containerId = await runDetached({\n image,\n mounts: [{ hostPath: spec.codeDir, containerPath: containerCodePath, readOnly: true }],\n extraMounts,\n env: spec.env,\n ...(spec.sensitiveEnvKeys !== undefined && { sensitiveEnvKeys: spec.sensitiveEnvKeys }),\n cmd: [spec.lambda.handler],\n hostPort,\n host: spec.containerHost,\n name,\n ...(spec.debugPort !== undefined && { debugPort: spec.debugPort }),\n ...(spec.tmpfs !== undefined && { tmpfs: spec.tmpfs }),\n ...(spec.extraHosts !== undefined && { extraHosts: spec.extraHosts }),\n });\n } else {\n // IMAGE branch (closes #453). The pre-built local tag is on\n // `spec.image`; the architecture-derived `--platform` is on\n // `spec.platform`. `ImageConfig` fields drive CMD / entrypoint /\n // workdir verbatim. No bind mounts: the image already contains\n // the function code at its built-in `/var/task`. AWS layers are\n // baked into the image at build time, not overlaid at runtime,\n // so we never emit a `/opt` mount on this branch (matches the\n // AWS-side invoke behavior). `tmpfs` (#440) applies inside any\n // container image just like on the public base images.\n containerId = await runDetached({\n image: spec.image,\n mounts: [],\n ...(spec.profileCredentialsFile && {\n extraMounts: [\n {\n hostPath: spec.profileCredentialsFile.hostPath,\n containerPath: spec.profileCredentialsFile.containerPath,\n readOnly: true,\n },\n ],\n }),\n env: spec.env,\n ...(spec.sensitiveEnvKeys !== undefined && { sensitiveEnvKeys: spec.sensitiveEnvKeys }),\n cmd: spec.command,\n hostPort,\n host: spec.containerHost,\n name,\n platform: spec.platform,\n ...(spec.entryPoint !== undefined && { entryPoint: spec.entryPoint }),\n ...(spec.workingDir !== undefined && { workingDir: spec.workingDir }),\n ...(spec.debugPort !== undefined && { debugPort: spec.debugPort }),\n ...(spec.tmpfs !== undefined && { tmpfs: spec.tmpfs }),\n ...(spec.extraHosts !== undefined && { extraHosts: spec.extraHosts }),\n });\n }\n const stopLogStream = streamingEnabled ? streamLogs(containerId) : (): void => undefined;\n try {\n await waitForRieReady(spec.containerHost, hostPort, 30_000);\n } catch (err) {\n // RIE didn't start — clean up before propagating.\n stopLogStream();\n await removeContainer(containerId).catch(() => undefined);\n throw err;\n }\n return {\n logicalId: spec.lambda.logicalId,\n containerId,\n containerName: name,\n hostPort,\n containerHost: spec.containerHost,\n stopLogStream,\n };\n }\n\n /**\n * Serialize a body of work behind the entry's growth mutex so two\n * `acquire()`s racing against the cap don't both try to lazy-start\n * (which would double the pool size + leak a container).\n */\n async function withMutex<T>(entry: ContainerPoolEntry, body: () => Promise<T>): Promise<T> {\n const previous = entry.growthMutex;\n let release!: () => void;\n entry.growthMutex = new Promise<void>((r) => (release = r));\n try {\n await previous;\n return await body();\n } finally {\n release();\n }\n }\n\n /**\n * Tear down one container; tolerate every kind of failure. Called from\n * the idle GC timer and from `dispose()`.\n */\n async function tearDown(handle: ContainerHandle): Promise<void> {\n try {\n handle.stopLogStream();\n } catch (err) {\n logger.debug(\n `stopLogStream(${handle.containerName}) failed: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n try {\n await removeContainer(handle.containerId);\n } catch (err) {\n logger.warn(\n `Failed to remove container ${handle.containerName}: ${err instanceof Error ? err.message : String(err)}. Continuing cleanup.`\n );\n }\n }\n\n function poolSize(entry: ContainerPoolEntry): number {\n return entry.warm.length + entry.inUse.size;\n }\n\n function resetIdleTimer(entry: ContainerPoolEntry): void {\n if (entry.idleTimer) {\n clearTimeout(entry.idleTimer);\n entry.idleTimer = null;\n }\n if (entry.warm.length === 0) return;\n entry.idleTimer = setTimeout(() => {\n void gcIdle(entry);\n }, idleMs);\n // Don't keep the Node event loop open just for the GC timer — when\n // the user hits ^C we want graceful shutdown to be able to exit.\n entry.idleTimer.unref?.();\n }\n\n /**\n * Idle GC: tear down every warm handle for the entry. Called by the\n * 60s timer; fired-and-forget so a slow `removeContainer` doesn't\n * block the timer queue.\n */\n async function gcIdle(entry: ContainerPoolEntry): Promise<void> {\n const handles = entry.warm.splice(0, entry.warm.length);\n entry.idleTimer = null;\n if (handles.length === 0) return;\n logger.debug(`Idle GC: tearing down ${handles.length} container(s) for ${entry.logicalId}`);\n await Promise.allSettled(handles.map((h) => tearDown(h)));\n }\n\n return {\n async acquire(logicalId: string): Promise<ContainerHandle> {\n const entry = entries.get(logicalId);\n if (!entry) {\n throw new Error(\n `containerPool.acquire: no spec registered for Lambda '${logicalId}'. This is a bug — every reachable route's Lambda should be registered at server boot.`\n );\n }\n\n // Fast path: an idle warm handle exists.\n if (entry.warm.length > 0) {\n const handle = entry.warm.shift()!;\n entry.inUse.add(handle);\n if (entry.idleTimer) {\n clearTimeout(entry.idleTimer);\n entry.idleTimer = null;\n }\n return handle;\n }\n\n // No idle handle. Either grow the pool (if below cap) or wait.\n // Grab the mutex to serialize the size check + grow decision.\n return await withMutex(entry, async () => {\n // Re-check the warm list inside the mutex — a concurrent\n // `release()` may have flipped a handle back to warm.\n if (entry.warm.length > 0) {\n const handle = entry.warm.shift()!;\n entry.inUse.add(handle);\n return handle;\n }\n\n if (poolSize(entry) < concurrencyCap) {\n const spec = specs.get(logicalId)!;\n // Track the start promise so `dispose()` can wait for it (with\n // a timeout) and tear down the resulting container instead of\n // leaking it on a SIGINT-during-cold-start race.\n const startPromise = startOne(spec);\n inFlightStarts.add(startPromise);\n let handle: ContainerHandle;\n try {\n handle = await startPromise;\n } finally {\n inFlightStarts.delete(startPromise);\n }\n entry.inUse.add(handle);\n return handle;\n }\n\n // At the cap — wait for a release.\n return await new Promise<ContainerHandle>((resolveAcquire, rejectAcquire) => {\n entry.waitQueue.push({ resolve: resolveAcquire, reject: rejectAcquire });\n });\n });\n },\n\n release(handle: ContainerHandle): void {\n const entry = entries.get(handle.logicalId);\n if (!entry) return;\n entry.inUse.delete(handle);\n\n // After dispose() started, never hand the handle off to a new\n // acquire() (the post-drain teardown is about to run). Push the\n // handle onto `warm` so dispose()'s post-drain harvest picks it\n // up for `removeContainer`. The idle GC timer is NOT re-armed\n // because dispose() has already cleared every entry's idleTimer\n // up front.\n if (disposed) {\n entry.warm.push(handle);\n if (entry.inUse.size === 0) {\n for (const resolve of entry.drainResolvers.splice(0, entry.drainResolvers.length)) {\n try {\n resolve();\n } catch {\n /* swallow */\n }\n }\n }\n return;\n }\n\n // Hand off to a waiting `acquire()` if any.\n const waiter = entry.waitQueue.shift();\n if (waiter) {\n entry.inUse.add(handle);\n waiter.resolve(handle);\n return;\n }\n\n // Otherwise return to the warm list and (re)arm the idle GC.\n entry.warm.push(handle);\n resetIdleTimer(entry);\n\n // If dispose() is waiting for this entry to drain, signal now.\n if (entry.inUse.size === 0 && entry.drainResolvers.length > 0) {\n for (const resolve of entry.drainResolvers.splice(0, entry.drainResolvers.length)) {\n try {\n resolve();\n } catch {\n /* swallow */\n }\n }\n }\n },\n\n async dispose(): Promise<void> {\n if (disposed) {\n logger.debug('Container pool dispose() called more than once; ignoring');\n return;\n }\n disposed = true;\n logger.debug('Disposing container pool');\n\n // Cancel idle timers and reject pending acquire-waiters up front;\n // those don't carry an in-flight request to wait on.\n for (const entry of entries.values()) {\n if (entry.idleTimer) {\n clearTimeout(entry.idleTimer);\n entry.idleTimer = null;\n }\n for (const waiter of entry.waitQueue.splice(0, entry.waitQueue.length)) {\n try {\n waiter.reject(\n new Error(`Container pool disposed while ${entry.logicalId} was waiting`)\n );\n } catch {\n /* swallow */\n }\n }\n }\n\n // Wait for every in-flight handle to release before tearing down\n // the underlying container. A request mid-`invokeRie` that gets\n // its container killed surfaces as a 502 — exactly the leak the\n // PR review caught. Bounded by `drainTimeoutMs` so a hung\n // request can't block shutdown forever; the verify.sh\n // `docker rm -f cdkl-*` sweep is the safety net for the\n // timeout case.\n const drainTimeoutMs = 30_000;\n const drainStart = Date.now();\n const entryDrains: Array<Promise<{ entry: ContainerPoolEntry; timedOut: boolean }>> = [];\n for (const entry of entries.values()) {\n if (entry.inUse.size === 0) continue;\n entryDrains.push(\n new Promise<{ entry: ContainerPoolEntry; timedOut: boolean }>((resolveDrain) => {\n entry.drainResolvers.push(() => resolveDrain({ entry, timedOut: false }));\n const t = setTimeout(() => {\n resolveDrain({ entry, timedOut: true });\n }, drainTimeoutMs);\n t.unref?.();\n })\n );\n }\n if (entryDrains.length > 0) {\n logger.debug(\n `Waiting for ${entryDrains.length} entry/entries' in-flight handle(s) to drain before teardown`\n );\n const drainResults = await Promise.all(entryDrains);\n let anyTimedOut = false;\n for (const r of drainResults) {\n if (r.timedOut) {\n anyTimedOut = true;\n logger.warn(\n `Container pool dispose timed out waiting for ${r.entry.inUse.size} in-flight handle(s) on ${r.entry.logicalId} after ${drainTimeoutMs}ms; tearing down anyway. The verify.sh \\`docker rm -f ${getEmbedConfig().resourceNamePrefix}-*\\` sweep is the safety net.`\n );\n }\n }\n if (!anyTimedOut) {\n logger.debug(`In-flight drain completed in ${Date.now() - drainStart}ms`);\n }\n }\n\n // Now harvest every handle the entry owns (warm + still-in-use\n // for the timed-out case) for teardown.\n const allHandles: ContainerHandle[] = [];\n for (const entry of entries.values()) {\n allHandles.push(...entry.warm.splice(0, entry.warm.length));\n for (const h of entry.inUse) allHandles.push(h);\n entry.inUse.clear();\n }\n\n // Wait for any cold-start `startOne` calls that were mid-flight at\n // dispose time, with a short timeout so a hung docker-run can't\n // block shutdown forever. Each settled start contributes its\n // resulting handle to the teardown set so the container does not\n // leak (the verify.sh `docker rm -f cdkl-*` sweep is a\n // safety net for the timeout case).\n const startPromises = [...inFlightStarts];\n if (startPromises.length > 0) {\n logger.debug(\n `Waiting for ${startPromises.length} in-flight container start(s) to settle before teardown`\n );\n const drainTimeoutMs = 5_000;\n const wrapped = startPromises.map((p) =>\n Promise.race([\n p.then((h): { kind: 'ok'; handle: ContainerHandle } => ({ kind: 'ok', handle: h })),\n new Promise<{ kind: 'timeout' }>((r) => {\n const t = setTimeout(() => r({ kind: 'timeout' }), drainTimeoutMs);\n t.unref?.();\n }),\n ]).catch((err: unknown) => {\n // `startOne` rejected — log and skip; nothing to tear down.\n logger.debug(\n `In-flight startOne rejected during dispose: ${err instanceof Error ? err.message : String(err)}`\n );\n return { kind: 'rejected' as const };\n })\n );\n const results = await Promise.all(wrapped);\n let timedOut = 0;\n for (const r of results) {\n if (r.kind === 'ok') {\n allHandles.push(r.handle);\n } else if (r.kind === 'timeout') {\n timedOut++;\n }\n }\n if (timedOut > 0) {\n logger.warn(\n `Container pool disposed with ${timedOut} in-flight start(s) still pending after ${drainTimeoutMs}ms; relying on docker --rm + the verify.sh sweep to clean up.`\n );\n }\n inFlightStarts.clear();\n }\n\n // Tear down in parallel; `tearDown` swallows individual failures.\n await Promise.allSettled(allHandles.map((h) => tearDown(h)));\n entries.clear();\n },\n };\n}\n\n/**\n * Validate / clamp the per-Lambda concurrency cap. Defense-in-depth: the\n * CLI parser also bounds the value, but this guarantees the pool stays\n * predictable when called programmatically (e.g. from tests).\n */\nfunction clampConcurrency(input: number): number {\n if (!Number.isFinite(input)) return 2;\n return Math.min(\n MAX_PER_LAMBDA_CONCURRENCY,\n Math.max(MIN_PER_LAMBDA_CONCURRENCY, Math.trunc(input))\n );\n}\n","import { randomUUID } from 'node:crypto';\nimport type { DiscoveredRoute } from './route-discovery.js';\n\n/**\n * HTTP request shape the event-builders consume. Decoupled from\n * `node:http`'s `IncomingMessage` so the builders are pure-functional and\n * unit-testable without a real socket.\n */\nexport interface HttpRequestSnapshot {\n /** HTTP method, uppercased (`GET` / `POST` / ...). */\n method: string;\n /**\n * Full URL path including query string, NOT decoded. Example:\n * `/items/123?foo=bar%20baz&foo=baz`.\n */\n rawUrl: string;\n /**\n * Headers as a key → array map (multiple values per name preserved).\n * Header names should be passed in their on-wire case; the builders\n * lowercase them per spec.\n */\n headers: Record<string, string[]>;\n /** Request body as a Buffer. Empty body → zero-length Buffer. */\n body: Buffer;\n /** The remote socket address (`socket.remoteAddress`). May be undefined. */\n sourceIp?: string;\n /**\n * Verified client certificate, populated only when the server is\n * running in mTLS mode (`https.createServer({requestCert: true,\n * rejectUnauthorized: true, ...})`) AND the TLS handshake succeeded.\n * Surfaced on the event under `requestContext.identity.clientCert`\n * (REST v1) and `requestContext.authentication.clientCert` (HTTP v2)\n * per AWS API Gateway's mutual-TLS event shape.\n *\n * Shape (matches AWS):\n * ```\n * {\n * clientCertPem: string, // PEM-encoded certificate\n * subjectDN: string, // \"CN=client,O=example,C=US\"\n * issuerDN: string, // \"CN=My CA,O=example,C=US\"\n * serialNumber: string, // \"01:23:45:...\" (hex)\n * validity: { notBefore: string, notAfter: string },\n * }\n * ```\n * The shape's exact key set is opaque to the event-builder — it is\n * passed through verbatim. The http-server module owns the\n * `PeerCertificate -> AWS shape` conversion via\n * `peerCertificateToAws`.\n */\n clientCert?: Record<string, unknown>;\n}\n\n/**\n * The matched route plus the path-parameter capture map produced by the\n * route-matcher. `matchedPath` is the literal request path that matched\n * (with placeholders substituted), used for `requestContext.http.path`.\n */\nexport interface MatchedRouteContext {\n route: DiscoveredRoute;\n pathParameters: Record<string, string>;\n /** The literal request path (e.g. `/items/123`) — not decoded. */\n matchedPath: string;\n}\n\nconst MOCK_ACCOUNT_ID = '123456789012';\nconst MOCK_DOMAIN_PREFIX = 'local';\nconst MOCK_DOMAIN_NAME = 'localhost';\nconst MOCK_API_ID = 'local';\n\n/**\n * Build the HTTP API v2 / Function URL event payload.\n *\n * Spec: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format\n *\n * Key points (C4 / C5 / C11 / C14):\n * - Header names are lowercased; duplicate-named headers are joined with\n * `,` into a single string (NOT an array).\n * - Cookies live in their own `cookies: string[]` field, separated from\n * `headers` by splitting the `cookie` header on `; `.\n * - `rawPath` / `rawQueryString` are NOT decoded.\n * - `pathParameters` / `queryStringParameters` values ARE\n * `decodeURIComponent`'d.\n * - `stageVariables: null` (explicit null, not undefined).\n * - `requestContext.authorizer: null` and `requestContext.authentication:\n * null` (explicit nulls — `\"authorizer\" in event.requestContext` is a\n * real check pattern in user code).\n * - `body` is base64 when the content is binary; otherwise UTF-8.\n * Heuristic: when the headers carry a textual content-type\n * (`text/*`, `application/json`, `application/xml`, `application/javascript`,\n * `application/x-www-form-urlencoded`, `application/graphql`), the body is\n * UTF-8; otherwise base64. Mirrors what API Gateway emits.\n */\nexport function buildHttpApiV2Event(\n req: HttpRequestSnapshot,\n ctx: MatchedRouteContext,\n opts: { now?: () => Date } = {}\n): Record<string, unknown> {\n const { rawPath, rawQueryString } = splitRawUrl(req.rawUrl);\n const { headers, cookies } = normalizeHeadersV2(req.headers);\n const queryStringParameters = parseQueryStringV2(rawQueryString);\n const userAgent = headers['user-agent'] ?? '';\n const contentType = headers['content-type'] ?? '';\n const { body, isBase64Encoded } = encodeBody(req.body, contentType);\n const now = opts.now ? opts.now() : new Date();\n\n const routeKey =\n ctx.route.pathPattern === '$default'\n ? '$default'\n : `${ctx.route.method} ${ctx.route.pathPattern}`;\n\n const event: Record<string, unknown> = {\n version: '2.0',\n routeKey,\n rawPath,\n rawQueryString,\n cookies,\n headers,\n queryStringParameters,\n pathParameters: decodePathParameters(ctx.pathParameters),\n // PR 8c: surface the route's resolved Stage Variables (or `null`\n // for routes without a Stage — Function URLs, plus HTTP API routes\n // when no Stage with matching variables was attached).\n stageVariables: ctx.route.stageVariables ?? null,\n requestContext: {\n accountId: MOCK_ACCOUNT_ID,\n apiId: MOCK_API_ID,\n domainName: MOCK_DOMAIN_NAME,\n domainPrefix: MOCK_DOMAIN_PREFIX,\n http: {\n method: req.method.toUpperCase(),\n path: ctx.matchedPath,\n protocol: 'HTTP/1.1',\n sourceIp: req.sourceIp ?? '127.0.0.1',\n userAgent,\n },\n requestId: randomUUID(),\n routeKey,\n stage: ctx.route.stage,\n time: formatRequestTime(now),\n timeEpoch: now.getTime(),\n // mTLS: when the server is in https + requestCert mode, the\n // verified peer certificate lands under `authentication.clientCert`\n // per AWS HTTP API's mTLS event shape. When not in mTLS mode,\n // `authentication` stays explicit-null (the pre-PR behavior — user\n // code that does `\"authentication\" in event.requestContext` keeps\n // seeing the field).\n authentication: req.clientCert ? { clientCert: req.clientCert } : null,\n authorizer: null,\n },\n body,\n isBase64Encoded,\n };\n return event;\n}\n\n/**\n * Build the REST v1 proxy event payload (the legacy shape used by\n * `AWS::ApiGateway::Method` integrations of type AWS_PROXY).\n *\n * Spec: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format\n *\n * Differences from v2:\n * - Header / query-string duplicates are kept in dedicated\n * `multiValueHeaders` / `multiValueQueryStringParameters` arrays\n * **alongside** the singular maps (the singular form keeps the LAST\n * value, matching API Gateway behavior).\n * - Cookies stay inline in the `cookie` header — there is no separate\n * `cookies` array.\n * - `requestContext` is REST-flavored (no `http` sub-object); identity\n * and stage live at the top level of requestContext.\n * - `pathParameters` may be `null` when there are none (matches AWS).\n */\nexport function buildRestV1Event(\n req: HttpRequestSnapshot,\n ctx: MatchedRouteContext,\n opts: { now?: () => Date } = {}\n): Record<string, unknown> {\n const { rawPath, rawQueryString } = splitRawUrl(req.rawUrl);\n const { singular: headers, multi: multiValueHeaders } = normalizeHeadersV1(req.headers);\n const { singular: queryStringParameters, multi: multiValueQueryStringParameters } =\n parseQueryStringV1(rawQueryString);\n const contentType = headers['content-type'] ?? '';\n const { body, isBase64Encoded } = encodeBody(req.body, contentType);\n const now = opts.now ? opts.now() : new Date();\n\n const pathParams = decodePathParameters(ctx.pathParameters);\n const event: Record<string, unknown> = {\n resource: ctx.route.pathPattern,\n path: ctx.matchedPath,\n httpMethod: req.method.toUpperCase(),\n headers,\n multiValueHeaders,\n queryStringParameters:\n Object.keys(queryStringParameters).length > 0 ? queryStringParameters : null,\n multiValueQueryStringParameters:\n Object.keys(multiValueQueryStringParameters).length > 0\n ? multiValueQueryStringParameters\n : null,\n pathParameters: Object.keys(pathParams).length > 0 ? pathParams : null,\n // PR 8c: surface the route's resolved Stage Variables (or `null`).\n // REST v1 hardcoded `null` pre-PR; with `attachStageContext` the\n // route now carries the deployed Stage's `Variables` map.\n stageVariables: ctx.route.stageVariables ?? null,\n requestContext: {\n accountId: MOCK_ACCOUNT_ID,\n apiId: MOCK_API_ID,\n domainName: MOCK_DOMAIN_NAME,\n domainPrefix: MOCK_DOMAIN_PREFIX,\n httpMethod: req.method.toUpperCase(),\n identity: {\n sourceIp: req.sourceIp ?? '127.0.0.1',\n userAgent: headers['user-agent'] ?? '',\n // mTLS: the verified peer certificate goes under\n // `requestContext.identity.clientCert` per AWS REST v1's\n // mutual-TLS event shape. Only surfaced when mTLS is active;\n // otherwise the key is omitted (matches deployed REST v1\n // behavior on non-mTLS APIs).\n ...(req.clientCert && { clientCert: req.clientCert }),\n },\n path: `/${ctx.route.stage}${ctx.matchedPath}`,\n protocol: 'HTTP/1.1',\n requestId: randomUUID(),\n requestTime: formatRequestTime(now),\n requestTimeEpoch: now.getTime(),\n resourcePath: ctx.route.pathPattern,\n stage: ctx.route.stage,\n authorizer: null,\n },\n body: req.body.length === 0 ? null : body,\n isBase64Encoded,\n };\n // C11: rawPath isn't part of the v1 spec but we keep the rawQueryString\n // line out — RestV1 doesn't expose it directly. Consumers wanting raw\n // values can re-decode the singular maps.\n void rawPath;\n return event;\n}\n\n/**\n * Authorizer payload shapes the http-server may set on the event after\n * the authorizer pass succeeded. The shape is dispatched on `kind`:\n *\n * - `'lambda-rest-v1'` — REST v1 Lambda authorizer. The `context` map\n * and (optional) `principalId` go under `event.requestContext.authorizer`.\n * Per the deployed shape, the `principalId` field is named `principalId`\n * and the context fields land flat alongside it.\n *\n * - `'lambda-http-v2'` — HTTP v2 Lambda authorizer. The same data goes\n * under `event.requestContext.authorizer.lambda`.\n *\n * - `'cognito-rest-v1'` — REST v1 Cognito authorizer. Claims land under\n * `event.requestContext.authorizer.claims`.\n *\n * - `'jwt-http-v2'` — HTTP v2 JWT authorizer. Claims land under\n * `event.requestContext.authorizer.jwt.claims`; the `scopes` array\n * mirrors AWS-deployed behavior (always present, may be empty).\n */\nexport type AuthorizerEventOverlay =\n | { kind: 'lambda-rest-v1'; principalId?: string; context?: Record<string, unknown> }\n | { kind: 'lambda-http-v2'; principalId?: string; context?: Record<string, unknown> }\n | { kind: 'cognito-rest-v1'; claims: Record<string, unknown> }\n | { kind: 'jwt-http-v2'; claims: Record<string, unknown>; scopes?: string[] };\n\n/**\n * Mutate `event.requestContext.authorizer` (and `.authorizer.lambda` /\n * `.authorizer.jwt` for v2) per {@link AuthorizerEventOverlay}. The\n * default `null` value is replaced with an object — user code that\n * checks `\"authorizer\" in event.requestContext` continues to see a\n * truthy value.\n *\n * Pure-functional: takes an event, returns a new event with the\n * overlay applied. Does not mutate the input.\n */\nexport function applyAuthorizerOverlay(\n event: Record<string, unknown>,\n overlay: AuthorizerEventOverlay\n): Record<string, unknown> {\n const requestContext =\n (event['requestContext'] as Record<string, unknown> | undefined) ??\n ({} as Record<string, unknown>);\n let authorizer: Record<string, unknown>;\n switch (overlay.kind) {\n case 'lambda-rest-v1': {\n authorizer = {\n ...(overlay.principalId !== undefined && { principalId: overlay.principalId }),\n ...(overlay.context ?? {}),\n };\n break;\n }\n case 'lambda-http-v2': {\n authorizer = {\n lambda: {\n ...(overlay.principalId !== undefined && { principalId: overlay.principalId }),\n ...(overlay.context ?? {}),\n },\n };\n break;\n }\n case 'cognito-rest-v1': {\n authorizer = { claims: { ...overlay.claims } };\n break;\n }\n case 'jwt-http-v2': {\n authorizer = {\n jwt: {\n claims: { ...overlay.claims },\n scopes: overlay.scopes ?? [],\n },\n };\n break;\n }\n }\n return {\n ...event,\n requestContext: {\n ...requestContext,\n authorizer,\n },\n };\n}\n\n/**\n * Split the request URL into `rawPath` (everything before `?`) and\n * `rawQueryString` (everything after, or `''`). Neither component is\n * decoded — that's the whole point of \"raw\" per the AWS spec.\n */\nfunction splitRawUrl(rawUrl: string): { rawPath: string; rawQueryString: string } {\n const q = rawUrl.indexOf('?');\n if (q === -1) return { rawPath: rawUrl, rawQueryString: '' };\n return { rawPath: rawUrl.slice(0, q), rawQueryString: rawUrl.slice(q + 1) };\n}\n\n/**\n * V2 header normalization (C14): lowercase every header name, comma-join\n * duplicate values into a single string, and split out the `cookie`\n * header into a `cookies` array (C5).\n */\nfunction normalizeHeadersV2(rawHeaders: Record<string, string[]>): {\n headers: Record<string, string>;\n cookies: string[];\n} {\n const headers: Record<string, string> = {};\n let cookies: string[] = [];\n for (const [name, values] of Object.entries(rawHeaders)) {\n const lower = name.toLowerCase();\n if (lower === 'cookie') {\n // Spec: split the request `Cookie:` header on `;` (with optional\n // surrounding whitespace) into individual `name=value` cookies.\n cookies = values\n .flatMap((v) => v.split(';'))\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n continue;\n }\n headers[lower] = values.join(',');\n }\n return { headers, cookies };\n}\n\n/**\n * V1 header normalization: lowercase every name and produce BOTH the\n * singular map (last value wins per AWS behavior) and the multi-value\n * map (every value preserved).\n */\nfunction normalizeHeadersV1(rawHeaders: Record<string, string[]>): {\n singular: Record<string, string>;\n multi: Record<string, string[]>;\n} {\n const singular: Record<string, string> = {};\n const multi: Record<string, string[]> = {};\n for (const [name, values] of Object.entries(rawHeaders)) {\n const lower = name.toLowerCase();\n if (values.length === 0) continue;\n multi[lower] = [...values];\n singular[lower] = values[values.length - 1]!;\n }\n return { singular, multi };\n}\n\n/**\n * V2 query-string parsing (C11 / C14): names and values are\n * `decodeURIComponent`'d; duplicate-named pairs are comma-joined into a\n * single value.\n */\nfunction parseQueryStringV2(rawQueryString: string): Record<string, string> {\n if (rawQueryString.length === 0) return {};\n const out: Record<string, string[]> = {};\n for (const pair of rawQueryString.split('&')) {\n if (pair.length === 0) continue;\n const eq = pair.indexOf('=');\n const rawKey = eq === -1 ? pair : pair.slice(0, eq);\n const rawValue = eq === -1 ? '' : pair.slice(eq + 1);\n const key = safeDecode(rawKey);\n const value = safeDecode(rawValue);\n if (!out[key]) out[key] = [];\n out[key].push(value);\n }\n const result: Record<string, string> = {};\n for (const [k, vs] of Object.entries(out)) result[k] = vs.join(',');\n return result;\n}\n\n/**\n * V1 query-string parsing: same decoding rules, but produce BOTH a\n * singular (last-wins) map and a multi-value map, matching the v1 event\n * shape.\n */\nfunction parseQueryStringV1(rawQueryString: string): {\n singular: Record<string, string>;\n multi: Record<string, string[]>;\n} {\n const multi: Record<string, string[]> = {};\n if (rawQueryString.length > 0) {\n for (const pair of rawQueryString.split('&')) {\n if (pair.length === 0) continue;\n const eq = pair.indexOf('=');\n const rawKey = eq === -1 ? pair : pair.slice(0, eq);\n const rawValue = eq === -1 ? '' : pair.slice(eq + 1);\n const key = safeDecode(rawKey);\n const value = safeDecode(rawValue);\n if (!multi[key]) multi[key] = [];\n multi[key].push(value);\n }\n }\n const singular: Record<string, string> = {};\n for (const [k, vs] of Object.entries(multi)) {\n if (vs.length > 0) singular[k] = vs[vs.length - 1]!;\n }\n return { singular, multi };\n}\n\n/**\n * Decode every value in a path-parameter map. Keys are always literal\n * names from the route pattern (no decoding needed); values come from the\n * URL and are `decodeURIComponent`'d per C11.\n */\nfunction decodePathParameters(pathParameters: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [k, v] of Object.entries(pathParameters)) {\n out[k] = safeDecode(v);\n }\n return out;\n}\n\n/**\n * Decode a URL component, falling back to the raw value when\n * `decodeURIComponent` throws on an invalid escape sequence (`%ZZ`).\n * Mirrors API Gateway's lenient behavior.\n */\nfunction safeDecode(s: string): string {\n try {\n return decodeURIComponent(s);\n } catch {\n return s;\n }\n}\n\n/**\n * Encode a Buffer as the event's `body` string + `isBase64Encoded` flag.\n *\n * Heuristic: textual content types pass through as UTF-8; everything else\n * is base64. The list of textual prefixes mirrors what real API Gateway\n * emits when the integration is a Lambda Proxy.\n */\nfunction encodeBody(body: Buffer, contentType: string): { body: string; isBase64Encoded: boolean } {\n if (body.length === 0) {\n return { body: '', isBase64Encoded: false };\n }\n if (isTextualContentType(contentType)) {\n return { body: body.toString('utf-8'), isBase64Encoded: false };\n }\n return { body: body.toString('base64'), isBase64Encoded: true };\n}\n\nconst TEXT_PREFIXES = [\n 'text/',\n 'application/json',\n 'application/xml',\n 'application/javascript',\n 'application/x-www-form-urlencoded',\n 'application/graphql',\n];\n\n/**\n * Whether the given `Content-Type` value indicates textual data (so the\n * event body should be UTF-8 instead of base64).\n */\nfunction isTextualContentType(contentType: string): boolean {\n if (!contentType) return false;\n const lower = contentType.toLowerCase();\n return TEXT_PREFIXES.some((p) => lower.startsWith(p));\n}\n\n/**\n * Format a Date as the API Gateway request-time string\n * (`10/May/2026:12:00:00 +0000`). Always emits UTC — the local server is\n * not timezone-aware and consistency is more useful than developer-locale\n * accuracy.\n */\nfunction formatRequestTime(d: Date): string {\n const months = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n ];\n const dd = String(d.getUTCDate()).padStart(2, '0');\n const mmm = months[d.getUTCMonth()]!;\n const yyyy = d.getUTCFullYear();\n const HH = String(d.getUTCHours()).padStart(2, '0');\n const MM = String(d.getUTCMinutes()).padStart(2, '0');\n const SS = String(d.getUTCSeconds()).padStart(2, '0');\n return `${dd}/${mmm}/${yyyy}:${HH}:${MM}:${SS} +0000`;\n}\n","/**\n * Translate a Lambda RIE response payload into HTTP response components\n * the local server can write back to the user.\n *\n * Three cases (C6 / C7):\n * 1. **Shaped response** — the payload has a `statusCode`. Use the\n * caller-provided status / headers / cookies / body / isBase64Encoded\n * directly.\n * 2. **Lambda runtime error envelope** — the payload has `errorMessage`\n * / `errorType` and **no statusCode**. The handler threw. Return HTTP\n * 502 with body `{\"message\":\"Internal server error\"}`. Stack trace\n * stays in the server log; the wire response does NOT leak it.\n * 3. **Auto-format heuristic** — the payload is \"valid JSON without\n * `statusCode`\". Wrap it as a 200 + JSON body with `content-type:\n * application/json`. (NOTE: the trigger is \"valid JSON\", NOT \"string\n * body\" — the pre-review draft was wrong on this.)\n *\n * Cookies in the shaped case (C5): the v2 spec uses a `cookies: string[]`\n * array which the server emits as **multiple** `Set-Cookie:` HTTP headers\n * (NOT comma-joined into one).\n */\n\nimport { stringifyValue } from '../utils/stringify.js';\n\nexport interface TranslatedHttpResponse {\n statusCode: number;\n /**\n * Single-valued headers: name → value. Names are lowercased and emitted\n * as-is. The `set-cookie` key is NOT in this map — cookies live in\n * `cookies` so the server can emit one header per entry.\n */\n headers: Record<string, string>;\n /** One value per cookie. Each is a full `Set-Cookie:` header value. */\n cookies: string[];\n /** Body bytes ready to write to the socket. */\n body: Buffer;\n}\n\n/**\n * Translate a Lambda RIE response into the HTTP components.\n *\n * @param payload The parsed JSON payload returned by RIE. Already-failed\n * JSON parses upstream surface as a malformed body and\n * callers should treat them as the error-envelope case\n * (HTTP 502); pass `undefined` here to land on the\n * auto-format branch with `body: ''`.\n * @param version Event version this response corresponds to. v1 ignores\n * the `cookies` array (REST v1 has no separate cookies\n * field — set-cookie is just another response header);\n * v2 separates them.\n */\nexport function translateLambdaResponse(\n payload: unknown,\n version: 'v1' | 'v2'\n): TranslatedHttpResponse {\n // (3) Lambda runtime error envelope → 502.\n if (isErrorEnvelope(payload)) {\n return errorEnvelopeResponse();\n }\n\n // (1) Shaped response.\n if (isShapedResponse(payload)) {\n return translateShapedResponse(payload, version);\n }\n\n // (2) Auto-format: any other JSON-able payload → 200 + JSON body.\n return autoFormatResponse(payload);\n}\n\n/**\n * Shape detection for the Lambda runtime's error response. RIE wraps a\n * thrown error in `{errorMessage, errorType, stackTrace, ...}`. Detection\n * relies on the `errorMessage` key + absence of `statusCode` — the latter\n * matters because user code MAY return a Lambda Proxy response that\n * happens to have `errorMessage` as a payload field.\n */\nfunction isErrorEnvelope(payload: unknown): boolean {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return false;\n const obj = payload as Record<string, unknown>;\n if ('statusCode' in obj) return false;\n return typeof obj['errorMessage'] === 'string';\n}\n\n/**\n * Shape detection for a Lambda Proxy / Function URL response — must be a\n * plain object with a numeric `statusCode`.\n */\nfunction isShapedResponse(payload: unknown): payload is Record<string, unknown> {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return false;\n const status = (payload as Record<string, unknown>)['statusCode'];\n return typeof status === 'number';\n}\n\n/**\n * Build the canonical 502 response for the Lambda-runtime-error case.\n * Body matches what real API Gateway returns when an integration response\n * fails; client code that fans out on `502 Bad Gateway` works\n * unchanged.\n */\nfunction errorEnvelopeResponse(): TranslatedHttpResponse {\n const body = Buffer.from('{\"message\":\"Internal server error\"}', 'utf-8');\n return {\n statusCode: 502,\n headers: {\n 'content-type': 'application/json',\n 'content-length': String(body.length),\n },\n cookies: [],\n body,\n };\n}\n\n/**\n * Translate a Lambda Proxy / Function URL response to HTTP components.\n *\n * Header normalization:\n * - The handler may emit headers under any case (`Content-Type`,\n * `content-type`, `CONTENT-TYPE`); we lowercase every name for the\n * wire write so the server's behavior is predictable.\n * - `multiValueHeaders` (v1 spec) are merged into the single map by\n * comma-joining their values. v2 doesn't surface multiValueHeaders;\n * `set-cookie` lives in the `cookies` array instead.\n * - `set-cookie` removed from `headers` and pushed onto `cookies` so\n * the server emits multiple `Set-Cookie:` lines (one per entry).\n */\nfunction translateShapedResponse(\n payload: Record<string, unknown>,\n version: 'v1' | 'v2'\n): TranslatedHttpResponse {\n const statusCode = Number(payload['statusCode']);\n const isBase64 = payload['isBase64Encoded'] === true;\n const rawBody = payload['body'];\n\n let body: Buffer;\n if (rawBody === undefined || rawBody === null) {\n body = Buffer.alloc(0);\n } else if (typeof rawBody === 'string') {\n body = isBase64 ? Buffer.from(rawBody, 'base64') : Buffer.from(rawBody, 'utf-8');\n } else {\n body = Buffer.from(JSON.stringify(rawBody), 'utf-8');\n }\n\n const headers: Record<string, string> = {};\n const cookies: string[] = [];\n\n // Singular headers map.\n const headersIn = payload['headers'];\n if (headersIn && typeof headersIn === 'object' && !Array.isArray(headersIn)) {\n for (const [name, value] of Object.entries(headersIn as Record<string, unknown>)) {\n const lower = name.toLowerCase();\n const stringValue = stringifyHeaderValue(value);\n if (lower === 'set-cookie') {\n cookies.push(stringValue);\n continue;\n }\n headers[lower] = stringValue;\n }\n }\n\n // v1: multiValueHeaders override / extend the singular map.\n if (version === 'v1') {\n const mvh = payload['multiValueHeaders'];\n if (mvh && typeof mvh === 'object' && !Array.isArray(mvh)) {\n for (const [name, values] of Object.entries(mvh as Record<string, unknown>)) {\n if (!Array.isArray(values)) continue;\n const lower = name.toLowerCase();\n const stringified = values.map((v) => stringifyHeaderValue(v));\n if (lower === 'set-cookie') {\n for (const c of stringified) cookies.push(c);\n continue;\n }\n headers[lower] = stringified.join(',');\n }\n }\n }\n\n // v2: cookies array (preferred form).\n if (version === 'v2') {\n const cookieList = payload['cookies'];\n if (Array.isArray(cookieList)) {\n for (const c of cookieList) {\n if (typeof c === 'string') cookies.push(c);\n }\n }\n }\n\n // content-length is informational; we always emit one based on the\n // actual body bytes so partial writes don't lie about the size.\n if (!('content-length' in headers)) {\n headers['content-length'] = String(body.length);\n }\n\n return { statusCode, headers, cookies, body };\n}\n\n/**\n * Wrap an unshaped payload as a 200 + JSON body. Triggered when the\n * response is \"valid JSON without statusCode\" — the C6 fix-up to the\n * pre-review draft's incorrect \"string body\" trigger.\n */\nfunction autoFormatResponse(payload: unknown): TranslatedHttpResponse {\n const body =\n payload === undefined ? Buffer.alloc(0) : Buffer.from(JSON.stringify(payload), 'utf-8');\n return {\n statusCode: 200,\n headers: {\n 'content-type': 'application/json',\n 'content-length': String(body.length),\n },\n cookies: [],\n body,\n };\n}\n\n/**\n * Stringify a header value while accepting the loose shapes user code\n * might return (`number`, `boolean`, `null`, etc.). Arrays are\n * comma-joined to match the same dup-coalesce rule used on the request\n * side.\n */\nfunction stringifyHeaderValue(value: unknown): string {\n if (value === null || value === undefined) return '';\n if (Array.isArray(value)) return value.map((v) => stringifyValue(v)).join(',');\n return stringifyValue(value);\n}\n","import type { DiscoveredRoute } from './route-discovery.js';\n\n/**\n * Match an incoming HTTP method + path against a list of discovered\n * routes (C10).\n *\n * Three tiers per AWS docs:\n * 1. **Full match** — every literal segment matches; `{name}` placeholders\n * capture one segment each. The most-literal route wins (best-effort\n * tie-break — see comment below; AWS does not formally specify\n * multi-route precedence within the same tier).\n * 2. **Greedy proxy** — the route ends in `{proxy+}` and the request path\n * starts with the literal prefix. The remainder of the path is captured\n * as the `proxy` parameter.\n * 3. **`$default`** — the catch-all RouteKey. Picked when nothing in tiers\n * 1 / 2 matched.\n *\n * Method matching: a route with method `'ANY'` matches every HTTP method;\n * method strings are compared case-insensitively otherwise.\n *\n * Returns `null` when no route matches.\n */\nexport interface RouteMatchResult {\n route: DiscoveredRoute;\n pathParameters: Record<string, string>;\n}\n\n/**\n * Match a request to the first applicable route per the 3-tier precedence\n * rule. Tier order is honored across the whole list — a tier-1 match in\n * any route always wins over every tier-2 / tier-3 match.\n */\nexport function matchRoute(\n method: string,\n requestPath: string,\n routes: readonly DiscoveredRoute[]\n): RouteMatchResult | null {\n const methodUpper = method.toUpperCase();\n\n // Strip any trailing slash beyond the root so `/items/` and `/items` match\n // the same route. AWS treats them identically.\n const normalizedPath = requestPath.length > 1 ? requestPath.replace(/\\/+$/, '') : requestPath;\n\n // Pre-split request segments once.\n const requestSegments = splitPath(normalizedPath);\n\n // Pass 1: full match.\n let bestFull: {\n route: DiscoveredRoute;\n pathParameters: Record<string, string>;\n literalCount: number;\n } | null = null;\n for (const route of routes) {\n if (!methodMatches(route.method, methodUpper)) continue;\n if (route.pathPattern === '$default') continue;\n if (isProxyRoute(route.pathPattern)) continue;\n const result = matchFullPattern(requestSegments, route.pathPattern);\n if (!result) continue;\n if (!bestFull || result.literalCount > bestFull.literalCount) {\n bestFull = {\n route,\n pathParameters: result.pathParameters,\n literalCount: result.literalCount,\n };\n }\n }\n if (bestFull) return { route: bestFull.route, pathParameters: bestFull.pathParameters };\n\n // Pass 2: greedy `{proxy+}`. Among multiple matches, prefer the one with\n // the longest literal prefix.\n let bestProxy: {\n route: DiscoveredRoute;\n pathParameters: Record<string, string>;\n literalCount: number;\n } | null = null;\n for (const route of routes) {\n if (!methodMatches(route.method, methodUpper)) continue;\n if (route.pathPattern === '$default') continue;\n if (!isProxyRoute(route.pathPattern)) continue;\n const result = matchProxyPattern(requestSegments, route.pathPattern);\n if (!result) continue;\n if (!bestProxy || result.literalCount > bestProxy.literalCount) {\n bestProxy = {\n route,\n pathParameters: result.pathParameters,\n literalCount: result.literalCount,\n };\n }\n }\n if (bestProxy) return { route: bestProxy.route, pathParameters: bestProxy.pathParameters };\n\n // Pass 3: `$default`.\n for (const route of routes) {\n if (!methodMatches(route.method, methodUpper)) continue;\n if (route.pathPattern === '$default') return { route, pathParameters: {} };\n }\n\n return null;\n}\n\n/**\n * Split a path into its non-empty segments. `/items/123` → `['items', '123']`;\n * `/` → `[]`.\n */\nfunction splitPath(path: string): string[] {\n return path.split('/').filter((s) => s.length > 0);\n}\n\n/**\n * Whether the route's HTTP method allows the incoming method. `'ANY'` is\n * the wildcard; otherwise compare case-insensitively.\n */\nfunction methodMatches(routeMethod: string, requestMethodUpper: string): boolean {\n if (routeMethod === 'ANY') return true;\n return routeMethod.toUpperCase() === requestMethodUpper;\n}\n\n/**\n * Whether the route's path pattern ends in `{proxy+}` (the greedy\n * placeholder). AWS only accepts the greedy placeholder as the LAST\n * segment of the pattern.\n */\nfunction isProxyRoute(pattern: string): boolean {\n return /\\/\\{[^/{}]+\\+\\}$/.test(pattern) || pattern === '/{proxy+}';\n}\n\n/**\n * Try to match a request against a non-proxy route pattern.\n *\n * Each segment of the pattern is either:\n * - a literal — must match the request segment exactly (case-sensitive,\n * AWS-spec-conformant);\n * - a `{name}` placeholder — captures one request segment as\n * `pathParameters.name`.\n *\n * Returns `null` on miss; on hit, returns the captured params plus the\n * count of literal segments (used by the caller as the tie-break heuristic\n * \"more literal segments wins\" within tier 1).\n */\nfunction matchFullPattern(\n requestSegments: readonly string[],\n pattern: string\n): { pathParameters: Record<string, string>; literalCount: number } | null {\n const patternSegments = splitPath(pattern);\n if (patternSegments.length !== requestSegments.length) return null;\n\n const pathParameters: Record<string, string> = {};\n let literalCount = 0;\n\n for (let i = 0; i < patternSegments.length; i++) {\n const ps = patternSegments[i]!;\n const rs = requestSegments[i]!;\n if (isPlaceholder(ps)) {\n const name = ps.slice(1, -1);\n pathParameters[name] = rs;\n } else if (ps === rs) {\n literalCount++;\n } else {\n return null;\n }\n }\n return { pathParameters, literalCount };\n}\n\n/**\n * Try to match a greedy-proxy route. The pattern's `{proxy+}` consumes\n * every remaining request segment; the literal prefix and any\n * `{name}` placeholders before it follow the same rules as\n * `matchFullPattern`.\n */\nfunction matchProxyPattern(\n requestSegments: readonly string[],\n pattern: string\n): { pathParameters: Record<string, string>; literalCount: number } | null {\n const patternSegments = splitPath(pattern);\n if (patternSegments.length === 0) return null;\n\n const tail = patternSegments[patternSegments.length - 1]!;\n if (!/^\\{[^/{}]+\\+\\}$/.test(tail)) return null;\n const proxyName = tail.slice(1, -2); // strip `{` ... `+}`\n\n const fixedPrefixLen = patternSegments.length - 1;\n if (requestSegments.length < fixedPrefixLen) return null;\n\n const pathParameters: Record<string, string> = {};\n let literalCount = 0;\n for (let i = 0; i < fixedPrefixLen; i++) {\n const ps = patternSegments[i]!;\n const rs = requestSegments[i]!;\n if (isPlaceholder(ps)) {\n pathParameters[ps.slice(1, -1)] = rs;\n } else if (ps === rs) {\n literalCount++;\n } else {\n return null;\n }\n }\n\n pathParameters[proxyName] = requestSegments.slice(fixedPrefixLen).join('/');\n return { pathParameters, literalCount };\n}\n\n/**\n * Whether a pattern segment is a single-segment placeholder (`{name}`\n * — NOT the greedy `{name+}` form, which is handled separately).\n */\nfunction isPlaceholder(segment: string): boolean {\n return /^\\{[^/{}+]+\\}$/.test(segment);\n}\n","/**\n * Shared per-kind context-shape builder for the authorizer pipeline.\n *\n * Today's only direct consumer is\n * `buildAuthorizerContextForServiceIntegration` in\n * [http-server.ts](./http-server.ts), which delegates to this helper to\n * produce `$context.authorizer.*` for HTTP API v2 service-integration\n * routes. The sibling `buildOverlay` in `http-server.ts` (used to\n * produce `event.requestContext.authorizer` for Lambda AWS_PROXY\n * routes via `applyAuthorizerOverlay`) still uses hand-rolled per-kind\n * branches because it wraps the result in the\n * `AuthorizerEventOverlay` discriminated union (with the\n * `kind === 'lambda-http-v2'` arm layering an additional `.lambda`\n * namespace that lives in the consumer, not here). The inner per-kind\n * context shape it builds matches this helper's output exactly, so a\n * future kind addition can be lifted through this helper at both call\n * sites with no behavior change.\n *\n * The returned shape is the BARE per-kind context the consumers want\n * — callers layer any consumed-shape-specific namespacing themselves.\n *\n * Per-kind shape:\n * - `lambda-token` / `lambda-request`: `principalId` (when set) plus\n * every key in `result.context` flat at the top.\n * - `iam`: `principalId` (when set) only — no IAM-context emulation.\n * - `cognito`: `{ claims: {...result.context} }` (REST v1 namespacing).\n * - `jwt`: `{ jwt: { claims: {...result.context}, scopes: [] } }`\n * (HTTP v2 namespacing; the scopes array always present per the\n * deployed shape).\n */\n\nimport type { AuthorizerInfo } from './authorizer-resolver.js';\nimport type { CachedAuthorizerResult } from './authorizer-cache.js';\n\n/**\n * Build the per-kind context shape for the authorizer pipeline. Returns\n * an empty object only when nothing in the result is surfaceable (e.g.\n * a Lambda authorizer with no `principalId` and no `context`); callers\n * decide whether to skip surfacing in that case.\n *\n * For Lambda kinds: callers may need to wrap the returned shape in a\n * `lambda` namespace for HTTP API v2 (`{ lambda: shape }`); the wrap\n * is consumer-specific so it's NOT done here. The shape returned is\n * the always-flat REST v1 form.\n */\nexport function buildAuthorizerContextShape(\n authorizer: AuthorizerInfo,\n result: CachedAuthorizerResult\n): Record<string, unknown> {\n if (authorizer.kind === 'lambda-token' || authorizer.kind === 'lambda-request') {\n const ctx: Record<string, unknown> = {};\n if (result.principalId !== undefined) ctx['principalId'] = result.principalId;\n if (result.context) Object.assign(ctx, result.context);\n return ctx;\n }\n if (authorizer.kind === 'iam') {\n const ctx: Record<string, unknown> = {};\n if (result.principalId !== undefined) ctx['principalId'] = result.principalId;\n return ctx;\n }\n if (authorizer.kind === 'cognito') {\n return { claims: { ...(result.context ?? {}) } };\n }\n // jwt\n return {\n jwt: {\n claims: { ...(result.context ?? {}) },\n scopes: [],\n },\n };\n}\n","import { invokeRie } from './rie-client.js';\nimport type { ContainerPool } from './container-pool.js';\nimport type { CachedAuthorizerResult } from './authorizer-cache.js';\nimport type {\n IdentitySourceSelector,\n LambdaRequestAuthorizer,\n LambdaTokenAuthorizer,\n} from './authorizer-resolver.js';\nimport { buildIdentityHash } from './authorizer-resolver.js';\n\n/**\n * Lambda authorizer (TOKEN + REQUEST) invocation for `cdkl start-api`.\n *\n * Both flavors invoke the authorizer Lambda via the same warm container\n * pool the route handlers use, then parse the response into a\n * {@link CachedAuthorizerResult}. The HTTP server layer feeds that result\n * to the per-request authorizer cache and (on Allow) propagates the\n * authorizer's `context` map into the route event.\n *\n * Spec references:\n * - REST v1 TOKEN:\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html\n * - REST v1 REQUEST:\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-lambda-authorizer-input.html\n * - HTTP v2 REQUEST:\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html\n */\n\nexport interface RequestSnapshotForAuthorizer {\n /** HTTP method (uppercased). */\n method: string;\n /** Lowercased headers as a key → joined-value map (V2 shape). */\n headers: Record<string, string>;\n /** Singular query-string map (last value wins). */\n queryStringParameters: Record<string, string>;\n /** Path parameters captured by the route matcher. */\n pathParameters: Record<string, string>;\n /** Source IP (typically `127.0.0.1` for the local server). */\n sourceIp: string;\n /** Path that matched the route (post-substitution). */\n matchedPath: string;\n /** API Gateway stage (REST v1: the configured stage name; HTTP v2: `$default`). */\n stage: string;\n}\n\nexport interface AuthorizerInvocationContext {\n /** Pool used to invoke the authorizer Lambda. */\n pool: ContainerPool;\n /** RIE invoke timeout. */\n rieTimeoutMs: number;\n /**\n * The methodArn the authorizer's policy evaluator compares against.\n * Built once per request as\n * `arn:aws:execute-api:local:<accountId>:<apiId>/<stage>/<METHOD>/<path>`.\n */\n methodArn: string;\n /** Mock account id (matches api-gateway-event.ts `MOCK_ACCOUNT_ID`). */\n mockAccountId: string;\n /** Mock api id (matches api-gateway-event.ts `MOCK_API_ID`). */\n mockApiId: string;\n}\n\n/**\n * Build the methodArn the authorizer's policy evaluator compares against.\n * Spec: `arn:aws:execute-api:<region>:<account>:<apiId>/<stage>/<METHOD>/<path>`.\n *\n * The path component MUST NOT have a leading slash; `/items/{id}` becomes\n * `items/{id}`. Method `ANY` is replaced with the actual request method\n * (matches AWS-deployed behavior).\n */\nexport function buildMethodArn(opts: {\n apiId: string;\n accountId: string;\n region?: string;\n stage: string;\n method: string;\n path: string;\n}): string {\n const region = opts.region ?? 'local';\n const trimmedPath = opts.path.replace(/^\\//, '');\n return `arn:aws:execute-api:${region}:${opts.accountId}:${opts.apiId}/${opts.stage}/${opts.method.toUpperCase()}/${trimmedPath}`;\n}\n\n/**\n * Invoke a TOKEN-type Lambda authorizer.\n *\n * The TOKEN event shape is the simplest of the three:\n * - `type: 'TOKEN'`\n * - `authorizationToken: <full header value>`\n * - `methodArn: <built by caller>`\n *\n * Returns `{ allow: false }` on:\n * - missing identity header (caller surfaces 401);\n * - authorizer Lambda throwing (caller surfaces 401 / 500 — TOKEN\n * authorizers map an error to 401 per the deployed behavior).\n */\nexport async function invokeTokenAuthorizer(\n authorizer: LambdaTokenAuthorizer,\n request: RequestSnapshotForAuthorizer,\n ctx: AuthorizerInvocationContext\n): Promise<CachedAuthorizerResult & { identityHash: string | undefined }> {\n const token = request.headers[authorizer.tokenHeader];\n if (!token || token.length === 0) {\n return { allow: false, identityHash: undefined };\n }\n\n const event = {\n type: 'TOKEN',\n authorizationToken: token,\n methodArn: ctx.methodArn,\n };\n\n const identityHash = buildIdentityHash([token]);\n const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);\n return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);\n}\n\n/**\n * Invoke a REQUEST-type Lambda authorizer.\n *\n * REST v1 and HTTP v2 use slightly different event shapes — the REST v1\n * shape mirrors the legacy proxy event; the HTTP v2 shape mirrors the\n * v2 proxy event. v1 / v2 distinction is carried on the authorizer\n * itself ({@link LambdaRequestAuthorizer.apiVersion}).\n */\nexport async function invokeRequestAuthorizer(\n authorizer: LambdaRequestAuthorizer,\n request: RequestSnapshotForAuthorizer,\n ctx: AuthorizerInvocationContext\n): Promise<CachedAuthorizerResult & { identityHash: string | undefined }> {\n // Build the cache key from every identity source the authorizer\n // declared. Missing values become empty strings; an authorizer with no\n // declared identity sources caches once globally (`identityHash = ''`).\n // REST v1: missing every identity source → 401 (matches deployed\n // behavior). HTTP v2 falls through with empty values; the authorizer\n // is then expected to deny on its own.\n const { identityHash, missing } = computeRequestIdentityHash(authorizer, request);\n if (missing) {\n return { allow: false, identityHash: undefined };\n }\n\n const event =\n authorizer.apiVersion === 'v1'\n ? buildRequestEventV1(authorizer, request, ctx)\n : buildRequestEventV2(authorizer, request, ctx);\n\n const result = await invokeAuthorizerLambda(authorizer.lambdaLogicalId, event, ctx);\n if (authorizer.apiVersion === 'v2') {\n // HTTP v2 supports two response shapes: simple ({isAuthorized: bool})\n // and IAM (policy document). cdk-local accepts both — the simple shape\n // wins when `isAuthorized` is present, otherwise we fall back to the\n // IAM-style policy parse.\n return parseHttpV2RequestResponse(result, ctx.methodArn, identityHash);\n }\n return parseLambdaAuthorizerResponse(result, ctx.methodArn, identityHash);\n}\n\n/**\n * Pick the value for one identity-source selector out of the request\n * snapshot. Returns `undefined` when the source isn't present in the\n * request (caller decides whether that's a deny or a pass-through).\n *\n * REST v1 supports `context.<name>` and `stageVariables.<name>` as\n * identity sources too. Local server has no realistic `requestContext`\n * sub-tree to query yet, and stage variables aren't populated in v1\n * (PR 8c will plumb them through), so both kinds currently return\n * `undefined`. Add a code comment when wiring stage variables — until\n * then, an authorizer whose ONLY identity source is a context /\n * stage-variable selector will still 401 the request on REST v1\n * (matching the deployed behavior when those sources are absent).\n */\nexport function extractIdentityValue(\n sel: IdentitySourceSelector,\n request: RequestSnapshotForAuthorizer\n): string | undefined {\n switch (sel.kind) {\n case 'header':\n return request.headers[sel.name];\n case 'query':\n return request.queryStringParameters[sel.name];\n case 'context':\n // Local server doesn't carry a realistic `requestContext` sub-tree\n // for v1; once available, look up `request.requestContext[sel.name]`\n // here. Until then, return undefined and surface the same 401 the\n // deployed behavior produces when the source is absent.\n return undefined;\n case 'stage-variable':\n // Stage variables become meaningful once PR 8c plumbs them through\n // — until then there is no map to look up against.\n return undefined;\n }\n}\n\n/**\n * Pre-compute the identity hash for a REQUEST authorizer from the request\n * snapshot, BEFORE invoking the Lambda. Used by the HTTP server's cache\n * lookup path so a hit can be served without paying for a Lambda invocation.\n *\n * Returns `undefined` when REST v1 has no usable identity source (caller\n * surfaces a 401 without invoking). HTTP v2 falls through with empty values\n * and the authorizer is expected to deny on its own.\n */\nexport function computeRequestIdentityHash(\n authorizer: LambdaRequestAuthorizer,\n request: RequestSnapshotForAuthorizer\n): { identityHash: string; missing: boolean } {\n const identityValues = authorizer.identitySources.map((sel) =>\n extractIdentityValue(sel, request)\n );\n const missing =\n authorizer.apiVersion === 'v1' &&\n authorizer.identitySources.length > 0 &&\n identityValues.every((v) => v === undefined || v === '');\n return { identityHash: buildIdentityHash(identityValues), missing };\n}\n\n/**\n * Build the REST v1 REQUEST authorizer event. Mirrors the deployed shape\n * (legacy proxy event minus the body).\n */\nfunction buildRequestEventV1(\n authorizer: LambdaRequestAuthorizer,\n request: RequestSnapshotForAuthorizer,\n ctx: AuthorizerInvocationContext\n): Record<string, unknown> {\n // Build the method-arn-shape `httpMethod` carries the actual method;\n // the deployed REST v1 authorizer event also carries `resource` and\n // `path`, which we set to the matched path for parity.\n return {\n type: 'REQUEST',\n methodArn: ctx.methodArn,\n resource: request.matchedPath,\n path: request.matchedPath,\n httpMethod: request.method,\n headers: request.headers,\n multiValueHeaders: Object.fromEntries(\n Object.entries(request.headers).map(([k, v]) => [k, v.split(',')])\n ),\n queryStringParameters: request.queryStringParameters,\n multiValueQueryStringParameters: Object.fromEntries(\n Object.entries(request.queryStringParameters).map(([k, v]) => [k, [v]])\n ),\n pathParameters: request.pathParameters,\n stageVariables: null,\n requestContext: {\n accountId: ctx.mockAccountId,\n apiId: ctx.mockApiId,\n httpMethod: request.method,\n identity: { sourceIp: request.sourceIp },\n path: `/${request.stage}${request.matchedPath}`,\n stage: request.stage,\n },\n authorizationToken: request.headers[authorizer.identitySources[0]?.name ?? 'authorization'],\n };\n}\n\n/**\n * Build the HTTP v2 REQUEST authorizer event.\n */\nfunction buildRequestEventV2(\n _authorizer: LambdaRequestAuthorizer,\n request: RequestSnapshotForAuthorizer,\n ctx: AuthorizerInvocationContext\n): Record<string, unknown> {\n return {\n version: '2.0',\n type: 'REQUEST',\n routeArn: ctx.methodArn,\n identitySource: [], // Honored by AWS but not interpreted by user code.\n routeKey: `${request.method} ${request.matchedPath}`,\n rawPath: request.matchedPath,\n rawQueryString: '',\n headers: request.headers,\n queryStringParameters: request.queryStringParameters,\n pathParameters: request.pathParameters,\n stageVariables: null,\n requestContext: {\n accountId: ctx.mockAccountId,\n apiId: ctx.mockApiId,\n domainName: 'localhost',\n domainPrefix: 'local',\n http: {\n method: request.method,\n path: request.matchedPath,\n protocol: 'HTTP/1.1',\n sourceIp: request.sourceIp,\n userAgent: request.headers['user-agent'] ?? '',\n },\n requestId: 'local-authorizer',\n routeKey: `${request.method} ${request.matchedPath}`,\n stage: request.stage,\n time: '',\n timeEpoch: 0,\n },\n };\n}\n\n/**\n * Send the event to the authorizer Lambda's RIE container.\n */\nasync function invokeAuthorizerLambda(\n lambdaLogicalId: string,\n event: unknown,\n ctx: AuthorizerInvocationContext\n): Promise<unknown> {\n const handle = await ctx.pool.acquire(lambdaLogicalId);\n try {\n const result = await invokeRie(handle.containerHost, handle.hostPort, event, ctx.rieTimeoutMs);\n return result.payload;\n } finally {\n ctx.pool.release(handle);\n }\n}\n\n/**\n * Parse a REST v1 / HTTP v2 IAM-style Lambda authorizer response. The\n * deployed shape is:\n *\n * {\n * \"principalId\": \"user|guest\",\n * \"policyDocument\": {\n * \"Version\": \"2012-10-17\",\n * \"Statement\": [{ \"Effect\": \"Allow\"|\"Deny\", \"Action\": ..., \"Resource\": ... }]\n * },\n * \"context\"?: { ... } // string-valued map\n * }\n *\n * cdk-local allows the response when ANY Allow statement's Resource matches\n * the methodArn (literal match OR wildcard at the trailing\n * `/<METHOD>/<path>` segment). A non-Allow / wildcard-mismatch / missing\n * policyDocument all map to deny.\n */\nexport function parseLambdaAuthorizerResponse(\n payload: unknown,\n methodArn: string,\n identityHash: string\n): CachedAuthorizerResult & { identityHash: string } {\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n return { allow: false, identityHash };\n }\n const obj = payload as Record<string, unknown>;\n const principalId = typeof obj['principalId'] === 'string' ? obj['principalId'] : undefined;\n const context =\n obj['context'] && typeof obj['context'] === 'object' && !Array.isArray(obj['context'])\n ? (obj['context'] as Record<string, unknown>)\n : undefined;\n\n const policy = obj['policyDocument'];\n if (!policy || typeof policy !== 'object') {\n return {\n allow: false,\n identityHash,\n ...(principalId !== undefined && { principalId }),\n ...(context && { context }),\n };\n }\n const stmts = (policy as Record<string, unknown>)['Statement'];\n if (!Array.isArray(stmts)) {\n return {\n allow: false,\n identityHash,\n ...(principalId !== undefined && { principalId }),\n ...(context && { context }),\n policy,\n };\n }\n\n const allow = stmts.some((stmt) => {\n if (!stmt || typeof stmt !== 'object' || Array.isArray(stmt)) return false;\n const s = stmt as Record<string, unknown>;\n if (s['Effect'] !== 'Allow') return false;\n const resources = Array.isArray(s['Resource']) ? s['Resource'] : [s['Resource']];\n return resources.some((r) => typeof r === 'string' && resourceMatches(r, methodArn));\n });\n\n return {\n allow,\n identityHash,\n ...(principalId !== undefined && { principalId }),\n ...(context && { context }),\n policy,\n };\n}\n\n/**\n * HTTP v2 simple-format response shape:\n * { \"isAuthorized\": true|false, \"context\"?: { ... } }\n *\n * Falls back to the IAM-style parse when `isAuthorized` is absent.\n */\nfunction parseHttpV2RequestResponse(\n payload: unknown,\n methodArn: string,\n identityHash: string\n): CachedAuthorizerResult & { identityHash: string } {\n if (payload && typeof payload === 'object' && !Array.isArray(payload)) {\n const obj = payload as Record<string, unknown>;\n if (typeof obj['isAuthorized'] === 'boolean') {\n const context =\n obj['context'] && typeof obj['context'] === 'object' && !Array.isArray(obj['context'])\n ? (obj['context'] as Record<string, unknown>)\n : undefined;\n return {\n allow: obj['isAuthorized'],\n identityHash,\n ...(context && { context }),\n };\n }\n }\n return parseLambdaAuthorizerResponse(payload, methodArn, identityHash);\n}\n\n/**\n * Match a Resource value against the methodArn. AWS supports `*` and `?`\n * glob characters in IAM policy resources; cdk-local implements the segmented\n * semantics AWS documents — `*` matches any sequence of characters\n * **within** a single segment, where segments are delimited by `:`\n * (ARN partition / service / region / account) and `/` (path). `**`\n * matches across segment boundaries for callers that need the looser\n * behavior (cdk-local-specific extension; no AWS equivalent, useful for\n * stage-wide rules like `arn:.../prod/**`).\n *\n * Examples:\n * - `arn:.../prod/GET/*` matches `arn:.../prod/GET/items` (single\n * trailing segment) but NOT `arn:.../prod/GET/items/42` (would cross\n * a `/`).\n * - `arn:.../prod/**` matches every method+path under `prod/`.\n * - `arn:.../prod/* / *` (no `**`, spaces inserted for readability)\n * matches `prod/GET/items` (two single-segment wildcards).\n *\n * Special case: a Resource ending in `/*` therefore allows any single\n * trailing segment, NOT arbitrarily-deep sub-paths — for the latter,\n * write `/**`. This is a behavior change vs the pre-fix permissive\n * implementation; deployed AWS policies that work in production should\n * not be affected because AWS itself applies the segmented rule.\n */\nexport function resourceMatches(pattern: string, methodArn: string): boolean {\n if (pattern === methodArn) return true;\n if (!pattern.includes('*') && !pattern.includes('?')) return false;\n // Translate the glob to a regex, taking care to handle `**` before `*`\n // so the cross-segment alternative is not greedy-consumed by the\n // single-segment rule.\n let regex = '';\n for (let i = 0; i < pattern.length; i++) {\n const ch = pattern[i]!;\n if (ch === '*') {\n if (pattern[i + 1] === '*') {\n // `**` — cross-segment match (`.*`)\n regex += '.*';\n i++;\n } else {\n // `*` — single segment (anything except `:` and `/`)\n regex += '[^:/]*';\n }\n } else if (ch === '?') {\n // `?` — any single character except `:` and `/` (segment-scoped)\n regex += '[^:/]';\n } else if ('.+^${}()|[]\\\\'.includes(ch)) {\n regex += '\\\\' + ch;\n } else {\n regex += ch;\n }\n }\n const re = new RegExp(`^${regex}$`);\n return re.test(methodArn);\n}\n\n/**\n * Re-evaluate a cached Lambda authorizer's policy document against the\n * current request's methodArn. Used by the HTTP server's cache hit path:\n * AWS-deployed API Gateway caches the IAM policy and re-checks `Resource`\n * against each new request's methodArn, so cdk-local mirrors that — caching\n * the verdict directly would let a narrow-Resource Allow leak across\n * routes.\n *\n * Returns the recomputed `allow` plus the existing `principalId` /\n * `context` carried on the cached entry. Returns `allow: false` when\n * the cached entry has no policy (not a Lambda authorizer) — caller\n * should not have called this in that case.\n */\nexport function evaluateCachedLambdaPolicy(\n cached: CachedAuthorizerResult,\n methodArn: string\n): CachedAuthorizerResult {\n const policy = cached.policy;\n if (!policy || typeof policy !== 'object') {\n return { ...cached, allow: false };\n }\n const stmts = (policy as Record<string, unknown>)['Statement'];\n if (!Array.isArray(stmts)) {\n return { ...cached, allow: false };\n }\n const allow = stmts.some((stmt) => {\n if (!stmt || typeof stmt !== 'object' || Array.isArray(stmt)) return false;\n const s = stmt as Record<string, unknown>;\n if (s['Effect'] !== 'Allow') return false;\n const resources = Array.isArray(s['Resource']) ? s['Resource'] : [s['Resource']];\n return resources.some((r) => typeof r === 'string' && resourceMatches(r, methodArn));\n });\n return { ...cached, allow };\n}\n","/**\n * SigV4 signature verification for REST v1 `AuthorizationType: 'AWS_IAM'`\n * authorizers (closes #447).\n *\n * # Scope\n *\n * cdk-local's `cdkl start-api` runs API Gateway routes locally. When a\n * route declares `AuthorizationType: 'AWS_IAM'`, AWS-deployed API Gateway\n * validates the request's SigV4 signature against the calling identity's\n * IAM permissions. We can't fully reproduce that locally — IAM policy\n * evaluation requires the deployed IAM data plane — so the local server\n * does the **signature-verification** half only:\n *\n * 1. Parse the `Authorization: AWS4-HMAC-SHA256 ...` header into the\n * `(credential, signedHeaders, signature)` triple.\n * 2. Reconstruct the canonical request per\n * <https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html>.\n * 3. Derive the signing key from the dev's **local** secret access key\n * (via the standard AWS SDK credential chain) + the request's date /\n * region / service scope.\n * 4. Compare the recomputed signature against the header's `signature`\n * value (constant-time compare).\n *\n * # Local-vs-deployed semantics\n *\n * Verification can only succeed when the request was signed with the\n * **same** credentials the local server can read. SigV4 is an HMAC\n * (shared-secret) signature: reproducing it requires the signer's secret\n * key. For a foreign access-key-id — a federated / Cognito Identity Pool /\n * cross-account signer — cdk-local does not have that secret and AWS never\n * publishes it, so local verification is **impossible**. The deployed API\n * Gateway CAN verify the very same request because AWS holds the secret;\n * this asymmetry is a local-only limitation, not a sign the request is\n * invalid.\n *\n * When the request's `Credential=AKID/...` scope names a different\n * access-key-id than the one the dev has locally (or no local credentials\n * resolve at all), we therefore **warn-and-pass by default**: allow + a\n * one-line warn + an obviously-fake principalId\n * (`unverified-foreign-identity` / `unverified-no-creds`). Local execution\n * is overwhelmingly used to exercise app logic and ergonomics, not to\n * reproduce an authorization boundary cdk-local cannot fully emulate\n * anyway — so blocking the most common legitimate case (federated /\n * Cognito Identity Pool / cross-account signers, which are foreign by\n * construction) is the wrong default. The fake principalId keeps\n * identity-based handler authz from silently trusting a forged caller, and\n * the deployed API Gateway still does the real verification + IAM\n * evaluation.\n *\n * The opt-in **`--strict-sigv4`** flag flips this to **fail-closed**: deny\n * unverifiable requests. Use it when you want local enforcement to mirror a\n * verified-identity assumption. OAC-fronted Function URLs always\n * warn-and-pass regardless of `--strict-sigv4` (CloudFront re-signs the\n * origin request in production, so no local client signature exists to\n * verify) and their warn lines reference CloudFront OAC.\n *\n * Genuinely missing / malformed signatures (no Authorization header,\n * unparseable header, wrong algorithm, stale date) **are** rejected in both\n * modes — those would never reach the deployed API either.\n *\n * # NOT IN SCOPE\n *\n * - IAM resource / action / condition policy evaluation. The local server\n * has no IAM data plane. Signature-verified callers reach the handler\n * under their own identity; downstream authorization is the dev's\n * responsibility.\n * - STS temporary credentials' session-token validation against AWS\n * (we accept whatever session-token the dev provides locally).\n * - Multi-account / cross-account signing — we verify against the local\n * default chain only.\n */\n\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto';\nimport { getLogger } from '../utils/logger.js';\nimport { buildIdentityHash } from './authorizer-resolver.js';\nimport { getEmbedConfig } from './embed-config.js';\nimport type { CachedAuthorizerResult } from './authorizer-cache.js';\n\n/**\n * The dev's resolved AWS credentials. Loaded lazily on first IAM-verify\n * call via the SDK default credential chain.\n */\nexport interface ResolvedCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string | undefined;\n}\n\n/**\n * Loader for the dev's local credentials. Wrapped in a function so tests\n * can inject a stub; the production loader uses the AWS SDK default\n * credential chain (env vars → ~/.aws/config → IMDS → ...).\n */\nexport type CredentialsLoader = () => Promise<ResolvedCredentials>;\n\n/**\n * Default credential loader: instantiates an `STSClient` (a direct cdk-local\n * dependency) and asks its built-in credential provider for the dev's\n * local credentials. STSClient uses the same Node default credential\n * chain (env vars → ~/.aws/config → IMDS → ...) every other AWS SDK call\n * in cdk-local uses, so this matches the deploy-time credential resolution\n * without adding a new dependency.\n */\nexport function defaultCredentialsLoader(): CredentialsLoader {\n let cached: Promise<ResolvedCredentials> | undefined;\n return () => {\n if (cached) return cached;\n cached = (async (): Promise<ResolvedCredentials> => {\n const { STSClient } = await import('@aws-sdk/client-sts');\n const client = new STSClient({});\n // STSClient typings (AWS SDK v3) expose `config.credentials` as a\n // memoized provider function; invoke it to resolve the dev's local\n // credentials via the default chain (env vars → ~/.aws/config →\n // IMDS → ...). The provider is cached internally by the SDK so\n // calling it multiple times is cheap.\n const creds = await client.config.credentials();\n client.destroy();\n return {\n accessKeyId: creds.accessKeyId,\n secretAccessKey: creds.secretAccessKey,\n sessionToken: creds.sessionToken,\n };\n })();\n return cached;\n };\n}\n\n/**\n * Snapshot of the inbound HTTP request needed to reconstruct the\n * canonical request. Matches the shape `runAuthorizerPass` already\n * builds for the other authorizer kinds.\n */\nexport interface SigV4VerifyRequest {\n /** HTTP method (uppercase). */\n method: string;\n /** Raw URL (path + optional query string). */\n rawUrl: string;\n /** Request headers as a single-value map (last value wins on duplicates). */\n headers: Record<string, string>;\n /** Body bytes (empty Buffer when GET / no body). */\n body: Buffer;\n}\n\n/**\n * Parsed Authorization header.\n *\n * Example header:\n * `AWS4-HMAC-SHA256 Credential=AKID/20260101/us-east-1/execute-api/aws4_request,\n * SignedHeaders=host;x-amz-date, Signature=abc...`\n */\ninterface ParsedAuthorization {\n algorithm: string;\n credentialAccessKeyId: string;\n credentialDate: string;\n credentialRegion: string;\n credentialService: string;\n credentialTerminator: string;\n signedHeaders: string[];\n signature: string;\n}\n\n/**\n * Outcome of {@link verifySigV4}. Matches the shape `runAuthorizerPass`\n * already produces for the other authorizer kinds so the http-server\n * cache + overlay paths reuse one record.\n */\nexport interface SigV4VerifyResult extends CachedAuthorizerResult {\n /** Hash for the per-`(authorizer, identity)` result cache. */\n identityHash: string | undefined;\n}\n\n/**\n * Verify the inbound request's `Authorization: AWS4-HMAC-SHA256 ...`\n * signature against the dev's local credentials.\n *\n * Outcomes:\n * - **No / malformed Authorization header** → `{allow: false}`. The\n * http-server maps this to 401 (REST v1 `missing-identity`).\n * - **Signature mismatch** under the dev's own credentials → `{allow: false}`.\n * The http-server maps this to 403 (REST v1 `policy-deny`).\n * - **Different `Credential` access-key-id than the dev has** (or no\n * local creds resolve) → unverifiable. `{allow: true}` plus a one-line\n * warn + fake principalId by default (warn-and-pass). With `strict`\n * (the `--strict-sigv4` flag) → `{allow: false}` (fail-closed).\n * - **Valid signature with the dev's credentials** → `{allow: true}`.\n * The principal id surfaced to the handler is the parsed\n * `Credential` access-key-id.\n * - **`oacFronted` route** → foreign / no-creds requests always pass\n * through regardless of `strict` (CloudFront re-signs origin requests\n * in production, so no local client signature can be verified) and the\n * warn lines reference CloudFront OAC.\n */\nexport async function verifySigV4(\n req: SigV4VerifyRequest,\n loadCredentials: CredentialsLoader,\n opts: {\n warnedForeignIds?: Set<string>;\n now?: () => Date;\n /**\n * Opt-in: when true, DENY unverifiable SigV4 requests (foreign\n * access-key-id, or local-credentials-load failure) instead of the\n * default warn-and-pass. DEFAULT: false (warn-and-pass) — local\n * execution is used to exercise app logic/ergonomics, not to reproduce\n * an auth boundary cdk-local cannot fully emulate, so blocking the\n * common federated / Cognito Identity Pool / cross-account case (foreign\n * by construction) is the wrong default. The fake principalId surfaced\n * on pass-through keeps identity-based handler authz from trusting a\n * forged caller. Flipped on by the `--strict-sigv4` CLI flag. Does NOT\n * apply to {@link oacFronted} routes (those always warn-and-pass).\n */\n strict?: boolean;\n /**\n * Set by the http-server for Function URL routes fronted by a CloudFront\n * Origin Access Control (see `isFunctionUrlOacFronted`). When true the\n * route always warn-and-passes regardless of {@link strict} (CloudFront\n * re-signs origin requests in production, so no local client signature\n * can be verified), and the warn-and-pass log lines reference CloudFront\n * OAC instead of `--strict-sigv4`.\n */\n oacFronted?: boolean;\n } = {}\n): Promise<SigV4VerifyResult> {\n const logger = getLogger();\n const authHeader = pickHeader(req.headers, 'authorization');\n if (!authHeader) {\n return { allow: false, identityHash: undefined };\n }\n\n let parsed: ParsedAuthorization;\n try {\n parsed = parseAuthorizationHeader(authHeader);\n } catch (err) {\n logger.debug(\n `AWS_IAM authorizer: malformed Authorization header — ${err instanceof Error ? err.message : String(err)}`\n );\n return { allow: false, identityHash: undefined };\n }\n\n if (parsed.algorithm !== 'AWS4-HMAC-SHA256') {\n logger.debug(`AWS_IAM authorizer: unsupported algorithm '${parsed.algorithm}'`);\n return { allow: false, identityHash: undefined };\n }\n if (parsed.credentialTerminator !== 'aws4_request') {\n logger.debug(\n `AWS_IAM authorizer: invalid credential scope terminator '${parsed.credentialTerminator}'`\n );\n return { allow: false, identityHash: undefined };\n }\n\n // The `x-amz-date` (or `date`) header must match the credential scope\n // date. We use `x-amz-date` when present (AWS SDK default), fall back\n // to `date` for compatibility with curl --aws-sigv4 etc.\n const amzDate = pickHeader(req.headers, 'x-amz-date') ?? pickHeader(req.headers, 'date');\n if (!amzDate) {\n logger.debug('AWS_IAM authorizer: missing x-amz-date / date header');\n return { allow: false, identityHash: undefined };\n }\n if (!validateAmzDateMatchesCredentialDate(amzDate, parsed.credentialDate)) {\n logger.debug(\n `AWS_IAM authorizer: x-amz-date '${amzDate}' does not match credential scope date '${parsed.credentialDate}'`\n );\n return { allow: false, identityHash: undefined };\n }\n\n // Optional clock-skew check: if the timestamp is more than 15 minutes\n // off the local clock, AWS rejects the request as expired. We mirror\n // that here — a missing `now` defaults to real time.\n const now = (opts.now ?? ((): Date => new Date()))();\n if (amzDateOutsideSkew(amzDate, now)) {\n logger.debug(`AWS_IAM authorizer: x-amz-date '${amzDate}' outside 15-min clock skew`);\n return { allow: false, identityHash: undefined };\n }\n\n // Load the dev's local credentials. Loader is cached so we hit the\n // credential chain at most once per server lifecycle.\n let local: ResolvedCredentials;\n try {\n local = await loadCredentials();\n } catch (err) {\n // Unverifiable: no local credentials resolved, so we cannot reproduce\n // the signing key. DEFAULT: warn-and-pass — local execution is for\n // exercising app logic / ergonomics, not reproducing an auth boundary\n // cdk-local cannot fully emulate. `--strict-sigv4` flips this to\n // fail-closed. OAC-fronted routes always pass (no client signature\n // exists to verify in production).\n const reason = err instanceof Error ? err.message : String(err);\n const { sigV4StrictByDefault, sigV4OptFlag: optFlag } = getEmbedConfig();\n if (opts.strict && !opts.oacFronted) {\n logger.warn(\n sigV4StrictByDefault\n ? `AWS_IAM authorizer: could not resolve local AWS credentials (${reason}), so the ` +\n `request's SigV4 signature cannot be verified. cdk-local denies unverifiable IAM ` +\n `requests by default; pass ${optFlag} to warn-and-pass, or configure AWS ` +\n `credentials cdk-local can read.`\n : `AWS_IAM authorizer: could not resolve local AWS credentials (${reason}), so the ` +\n `request's SigV4 signature cannot be verified. ${optFlag} is set, so cdk-local ` +\n `denies unverifiable IAM requests; remove ${optFlag} to warn-and-pass (the ` +\n `default), or configure AWS credentials cdk-local can read.`\n );\n return { allow: false, identityHash: undefined };\n }\n logger.warn(\n opts.oacFronted\n ? `AWS_IAM authorizer: Function URL is fronted by CloudFront OAC (CloudFront re-signs origin requests in production), and local AWS credentials could not be resolved (${reason}). Passing through with unverified principalId 'unverified-no-creds'. Do NOT trust event.requestContext.identity.accessKey in handler code.`\n : sigV4StrictByDefault\n ? `AWS_IAM authorizer: could not resolve local AWS credentials (${reason}), so the request's SigV4 signature cannot be verified locally (SigV4 is an HMAC shared-secret signature; the deployed API Gateway verifies it against AWS's copy of the secret). ${optFlag} is set; passing through with unverified principalId 'unverified-no-creds'. Do NOT trust event.requestContext.identity.accessKey in handler code.`\n : `AWS_IAM authorizer: could not resolve local AWS credentials (${reason}), so the request's SigV4 signature cannot be verified locally (SigV4 is an HMAC shared-secret signature; the deployed API Gateway verifies it against AWS's copy of the secret). Passing through with unverified principalId 'unverified-no-creds' — cdk-local's default for unverifiable IAM requests; pass ${optFlag} to deny instead. Do NOT trust event.requestContext.identity.accessKey in handler code.`\n );\n return {\n allow: true,\n // Surface an obviously-fake principalId so handlers cannot be\n // fooled into trusting the unverified access-key-id.\n principalId: 'unverified-no-creds',\n identityHash: buildIdentityHash([parsed.signature]),\n };\n }\n\n // Foreign-identity request: the signer used an access key id we don't\n // have, so we can't reproduce the signing key — verification is\n // impossible. DEFAULT: warn-and-pass with a fake principalId (the common\n // legitimate case is a federated / Cognito Identity Pool / cross-account\n // signer, foreign by construction). `--strict-sigv4` flips this to\n // fail-closed: deny, since a forged `Authorization: AWS4-HMAC-SHA256\n // Credential=AKID-X/...` header could otherwise be admitted as principal\n // `AKID-X` to handler code that trusts the request identity. Use\n // case-insensitive compare on the access key id — AWS docs are silent and\n // a lowercased AKID is a trivial bypass vector otherwise.\n if (local.accessKeyId.toLowerCase() !== parsed.credentialAccessKeyId.toLowerCase()) {\n const warned = opts.warnedForeignIds;\n // The dedup key MUST be normalized to match the case-insensitive\n // AKID compare above — otherwise an attacker probing variants\n // (AKIDFOREIGN, akidforeign, AkIdFOREIGN) would trigger a fresh\n // warn line per case. Case-insensitive compare → case-insensitive\n // dedup. (PR #484 review MINOR.)\n const dedupKey = parsed.credentialAccessKeyId.toLowerCase();\n const { sigV4StrictByDefault, sigV4OptFlag: optFlag } = getEmbedConfig();\n if (opts.strict && !opts.oacFronted) {\n if (!warned || !warned.has(dedupKey)) {\n logger.warn(\n sigV4StrictByDefault\n ? `AWS_IAM authorizer: request signed with access-key-id '${parsed.credentialAccessKeyId}', ` +\n `which differs from the AWS credentials cdk-local resolved locally — SigV4 (HMAC / ` +\n `shared-secret) can only be verified with the signer's own credentials, never a ` +\n `federated / Cognito Identity Pool / cross-account signer's. cdk-local denies it by ` +\n `default; pass ${optFlag} to warn-and-pass, or sign the request with the same ` +\n `credentials cdk-local resolves locally.`\n : `AWS_IAM authorizer: request signed with access-key-id '${parsed.credentialAccessKeyId}', ` +\n `which differs from the AWS credentials cdk-local resolved locally — SigV4 (HMAC / ` +\n `shared-secret) can only be verified with the signer's own credentials, never a ` +\n `federated / Cognito Identity Pool / cross-account signer's. ${optFlag} is set, so ` +\n `cdk-local denies it; remove ${optFlag} to warn-and-pass (the default), or sign the ` +\n `request with the same credentials cdk-local resolves locally.`\n );\n warned?.add(dedupKey);\n }\n return { allow: false, identityHash: undefined };\n }\n if (!warned || !warned.has(dedupKey)) {\n logger.warn(\n opts.oacFronted\n ? `AWS_IAM authorizer: Function URL is fronted by CloudFront OAC — in production CloudFront re-signs the origin request, so the local client's signature (access-key-id '${parsed.credentialAccessKeyId}') cannot be verified. ` +\n `Passing through with unverified principalId 'unverified-foreign-identity'. ` +\n `Do NOT trust event.requestContext.authorizer.principalId in handler code.`\n : sigV4StrictByDefault\n ? `AWS_IAM authorizer: request signed with access-key-id '${parsed.credentialAccessKeyId}', ` +\n `a federated / Cognito Identity Pool / cross-account signer cdk-local cannot verify ` +\n `locally (SigV4 is an HMAC shared-secret signature; the deployed API Gateway verifies ` +\n `it because AWS holds the secret). ${optFlag} is set; passing through with unverified ` +\n `principalId 'unverified-foreign-identity'. Do NOT trust ` +\n `event.requestContext.authorizer.principalId in handler code.`\n : `AWS_IAM authorizer: request signed with access-key-id '${parsed.credentialAccessKeyId}', ` +\n `a federated / Cognito Identity Pool / cross-account signer cdk-local cannot verify ` +\n `locally (SigV4 is an HMAC shared-secret signature; the deployed API Gateway verifies ` +\n `it because AWS holds the secret). Passing through with unverified principalId ` +\n `'unverified-foreign-identity' — cdk-local's default for unverifiable IAM requests; ` +\n `pass ${optFlag} to deny instead. Do NOT trust ` +\n `event.requestContext.authorizer.principalId in handler code.`\n );\n warned?.add(dedupKey);\n }\n return {\n allow: true,\n // Surface an obviously-fake principalId so handler code cannot\n // be fooled into trusting the unverified access-key-id.\n principalId: 'unverified-foreign-identity',\n identityHash: buildIdentityHash([parsed.signature]),\n };\n }\n\n // Same identity — reproduce the canonical request, derive the signing\n // key, recompute the signature, compare.\n const recomputed = computeSignature(req, parsed, local.secretAccessKey, amzDate);\n if (!constantTimeEqual(recomputed, parsed.signature)) {\n logger.debug(\n `AWS_IAM authorizer: signature mismatch (expected '${recomputed}', got '${parsed.signature}')`\n );\n return { allow: false, identityHash: undefined };\n }\n\n return {\n allow: true,\n principalId: parsed.credentialAccessKeyId,\n identityHash: buildIdentityHash([parsed.signature]),\n };\n}\n\n/**\n * Parse `AWS4-HMAC-SHA256 Credential=..., SignedHeaders=..., Signature=...`.\n * Rejects every other shape (including legacy `AWS4-HMAC-SHA256-...`\n * variants and HTTP/1.0-style multi-line values).\n */\nexport function parseAuthorizationHeader(value: string): ParsedAuthorization {\n const spaceIdx = value.indexOf(' ');\n if (spaceIdx < 0) {\n throw new Error('expected algorithm followed by parameters');\n }\n const algorithm = value.slice(0, spaceIdx).trim();\n const rest = value.slice(spaceIdx + 1).trim();\n\n // Split by commas; each piece is `Key=Value`. Whitespace around commas\n // is permitted by the AWS spec.\n const parts = rest.split(',').map((s) => s.trim());\n const fields: Record<string, string> = {};\n for (const part of parts) {\n const eq = part.indexOf('=');\n if (eq < 0) throw new Error(`malformed parameter '${part}'`);\n const key = part.slice(0, eq).trim();\n const val = part.slice(eq + 1).trim();\n fields[key] = val;\n }\n\n const credential = fields['Credential'];\n const signedHeaders = fields['SignedHeaders'];\n const signature = fields['Signature'];\n if (!credential) throw new Error('missing Credential');\n if (!signedHeaders) throw new Error('missing SignedHeaders');\n if (!signature) throw new Error('missing Signature');\n\n // Credential format: AKID/YYYYMMDD/region/service/aws4_request\n const credParts = credential.split('/');\n if (credParts.length !== 5) {\n throw new Error(`malformed Credential '${credential}' (expected 5 slash-separated segments)`);\n }\n const [accessKeyId, date, region, service, terminator] = credParts as [\n string,\n string,\n string,\n string,\n string,\n ];\n\n if (!/^[0-9]{8}$/.test(date)) {\n throw new Error(`malformed credential date '${date}' (expected YYYYMMDD)`);\n }\n\n return {\n algorithm,\n credentialAccessKeyId: accessKeyId,\n credentialDate: date,\n credentialRegion: region,\n credentialService: service,\n credentialTerminator: terminator,\n signedHeaders: signedHeaders.split(';').map((h) => h.trim().toLowerCase()),\n signature: signature.toLowerCase(),\n };\n}\n\n/**\n * AWS SigV4 canonical-request computation. Per\n * <https://docs.aws.amazon.com/IAM/latest/UserGuide/create-signed-request.html>:\n *\n * CanonicalRequest =\n * HTTPRequestMethod + '\\n' +\n * CanonicalURI + '\\n' +\n * CanonicalQueryString + '\\n' +\n * CanonicalHeaders + '\\n' +\n * SignedHeaders + '\\n' +\n * HexEncode(Hash(RequestPayload))\n *\n * Then:\n * StringToSign = \"AWS4-HMAC-SHA256\\n\" + AmzDate + \"\\n\" +\n * CredentialScope + \"\\n\" +\n * HexEncode(Hash(CanonicalRequest))\n *\n * SigningKey = HMAC(HMAC(HMAC(HMAC(\"AWS4\"+Secret, Date), Region), Service), \"aws4_request\")\n * Signature = HexEncode(HMAC(SigningKey, StringToSign))\n */\nfunction computeSignature(\n req: SigV4VerifyRequest,\n parsed: ParsedAuthorization,\n secretAccessKey: string,\n amzDate: string\n): string {\n const { path, query } = splitRawUrl(req.rawUrl);\n const canonicalUri = canonicalizePath(path);\n const canonicalQuery = canonicalizeQueryString(query);\n\n // Build canonical headers from the signedHeaders list — every named\n // header MUST be present (we reject early when missing). Values are\n // trimmed of leading/trailing whitespace and internal runs of spaces\n // collapsed to a single space (per the AWS spec).\n const headerLines: string[] = [];\n for (const name of parsed.signedHeaders) {\n const raw = pickHeader(req.headers, name);\n if (raw === undefined) {\n // Missing signed header → recompute will fail and the compare\n // returns false. We still produce a sentinel string so the caller\n // gets a deterministic \"no match\" rather than a thrown error.\n return 'missing-signed-header';\n }\n headerLines.push(`${name}:${normalizeHeaderValue(raw)}\\n`);\n }\n const canonicalHeaders = headerLines.join('');\n const signedHeadersStr = parsed.signedHeaders.join(';');\n\n // Payload hash: AWS SigV4 supports an UNSIGNED-PAYLOAD marker (used by\n // streaming uploads); the inbound request's `x-amz-content-sha256`\n // header carries it. Fall back to hashing the actual body.\n const xAmzContentSha = pickHeader(req.headers, 'x-amz-content-sha256');\n const payloadHash =\n xAmzContentSha &&\n (xAmzContentSha === 'UNSIGNED-PAYLOAD' || /^[0-9a-f]{64}$/i.test(xAmzContentSha))\n ? xAmzContentSha.toLowerCase()\n : sha256Hex(req.body);\n\n const canonicalRequest = [\n req.method.toUpperCase(),\n canonicalUri,\n canonicalQuery,\n canonicalHeaders,\n signedHeadersStr,\n payloadHash,\n ].join('\\n');\n\n const credentialScope = `${parsed.credentialDate}/${parsed.credentialRegion}/${parsed.credentialService}/${parsed.credentialTerminator}`;\n const stringToSign = [\n 'AWS4-HMAC-SHA256',\n amzDate,\n credentialScope,\n sha256Hex(Buffer.from(canonicalRequest, 'utf8')),\n ].join('\\n');\n\n const kDate = hmac(`AWS4${secretAccessKey}`, parsed.credentialDate);\n const kRegion = hmac(kDate, parsed.credentialRegion);\n const kService = hmac(kRegion, parsed.credentialService);\n const kSigning = hmac(kService, 'aws4_request');\n return hmac(kSigning, stringToSign).toString('hex');\n}\n\nfunction hmac(key: string | Buffer, data: string): Buffer {\n return createHmac('sha256', key).update(data, 'utf8').digest();\n}\n\nfunction sha256Hex(buf: Buffer): string {\n return createHash('sha256').update(buf).digest('hex');\n}\n\n/**\n * Split a raw URL into (decoded path, raw query string).\n *\n * Important: keep the path RAW for canonicalization — the canonicalizer\n * does its own URI-encoding so we do NOT decode here.\n */\nfunction splitRawUrl(rawUrl: string): { path: string; query: string } {\n const q = rawUrl.indexOf('?');\n if (q < 0) return { path: rawUrl, query: '' };\n return { path: rawUrl.slice(0, q), query: rawUrl.slice(q + 1) };\n}\n\n/**\n * Canonicalize the request path per the AWS SigV4 spec:\n *\n * - URI-encode each path segment (reserved chars are percent-encoded\n * EXCEPT `-_.~` which stay literal).\n * - Encode `/` between segments unchanged.\n * - Empty path → `/`.\n *\n * This matches the `execute-api` service's signing rules (no double-\n * encoding).\n */\nexport function canonicalizePath(path: string): string {\n if (!path || path === '') return '/';\n // The request path may already be percent-encoded from the wire. The\n // AWS SDK's signer normalizes by SINGLE-encoding the decoded path —\n // we mirror that.\n const decoded = path\n .split('/')\n .map((seg) => {\n try {\n return decodeURIComponent(seg);\n } catch {\n return seg;\n }\n })\n .join('/');\n return decoded\n .split('/')\n .map((seg) => sigV4EncodePathSegment(seg))\n .join('/');\n}\n\n/**\n * Encode a single path segment per the SigV4 unreserved-set rules:\n * `A-Za-z0-9-_.~` stay literal; everything else is percent-encoded.\n */\nfunction sigV4EncodePathSegment(seg: string): string {\n return seg.replace(/[^A-Za-z0-9\\-_.~]/g, (ch) => {\n // Use encodeURIComponent and then upper-case the hex digits (AWS\n // canonical form uses upper-case hex).\n const enc = encodeURIComponent(ch);\n return enc.replace(/%[0-9a-f]{2}/g, (s) => s.toUpperCase());\n });\n}\n\n/**\n * Canonicalize the query string per SigV4: parse `key=value` pairs,\n * SORT by key (then by value on collisions), URI-encode each side\n * with upper-case hex, join with `&`.\n */\nexport function canonicalizeQueryString(query: string): string {\n if (!query) return '';\n const pairs: Array<[string, string]> = [];\n for (const raw of query.split('&')) {\n if (!raw) continue;\n const eq = raw.indexOf('=');\n const [k, v] = eq < 0 ? [raw, ''] : [raw.slice(0, eq), raw.slice(eq + 1)];\n let dk: string;\n let dv: string;\n try {\n dk = decodeURIComponent(k.replace(/\\+/g, ' '));\n } catch {\n dk = k;\n }\n try {\n dv = decodeURIComponent(v.replace(/\\+/g, ' '));\n } catch {\n dv = v;\n }\n pairs.push([sigV4EncodeQuery(dk), sigV4EncodeQuery(dv)]);\n }\n pairs.sort((a, b) => {\n if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;\n return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0;\n });\n return pairs.map(([k, v]) => `${k}=${v}`).join('&');\n}\n\nfunction sigV4EncodeQuery(s: string): string {\n return s.replace(/[^A-Za-z0-9\\-_.~]/g, (ch) => {\n const enc = encodeURIComponent(ch);\n return enc.replace(/%[0-9a-f]{2}/g, (m) => m.toUpperCase());\n });\n}\n\n/**\n * Trim leading/trailing whitespace and collapse internal runs of\n * whitespace to a single space, per the SigV4 spec.\n */\nfunction normalizeHeaderValue(value: string): string {\n return value.trim().replace(/\\s+/g, ' ');\n}\n\nfunction pickHeader(headers: Record<string, string>, name: string): string | undefined {\n const lower = name.toLowerCase();\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === lower) return v;\n }\n return undefined;\n}\n\n/**\n * Compare two hex-encoded signatures in constant time. Returns false\n * when the lengths differ (the standard short-circuit, since timing\n * leaks on length are inherent to comparing values of different sizes).\n */\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n const ab = Buffer.from(a, 'hex');\n const bb = Buffer.from(b, 'hex');\n // Buffer.from('zz', 'hex') silently returns an empty buffer; guard\n // against that by checking the expected length matches what we got.\n if (ab.length !== bb.length || ab.length === 0) return false;\n return timingSafeEqual(ab, bb);\n}\n\n/**\n * AWS SigV4 expects `x-amz-date` in ISO8601 basic form `YYYYMMDDTHHMMSSZ`.\n * The credential scope encodes only the date portion. We accept both\n * `x-amz-date` and the legacy `date` header (RFC 7231) for compat.\n */\nexport function validateAmzDateMatchesCredentialDate(\n amzDate: string,\n credentialDate: string\n): boolean {\n // ISO8601 basic: YYYYMMDDTHHMMSSZ\n const isoMatch = /^(\\d{8})T\\d{6}Z$/.exec(amzDate);\n if (isoMatch) {\n return isoMatch[1] === credentialDate;\n }\n // RFC 7231: Mon, 02 Jan 2006 15:04:05 GMT\n try {\n const parsed = new Date(amzDate);\n if (Number.isNaN(parsed.getTime())) return false;\n const yyyy = parsed.getUTCFullYear().toString().padStart(4, '0');\n const mm = (parsed.getUTCMonth() + 1).toString().padStart(2, '0');\n const dd = parsed.getUTCDate().toString().padStart(2, '0');\n return `${yyyy}${mm}${dd}` === credentialDate;\n } catch {\n return false;\n }\n}\n\n/**\n * Reject SigV4 timestamps more than 15 minutes off the local clock —\n * matches AWS-deployed behavior (the `RequestTimeTooSkewed` error).\n */\nexport function amzDateOutsideSkew(amzDate: string, now: Date): boolean {\n const iso = /^(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})Z$/.exec(amzDate);\n let ts: Date;\n if (iso) {\n ts = new Date(\n Date.UTC(\n Number(iso[1]),\n Number(iso[2]) - 1,\n Number(iso[3]),\n Number(iso[4]),\n Number(iso[5]),\n Number(iso[6])\n )\n );\n } else {\n ts = new Date(amzDate);\n }\n if (Number.isNaN(ts.getTime())) return true;\n const deltaMs = Math.abs(ts.getTime() - now.getTime());\n return deltaMs > 15 * 60 * 1000;\n}\n","/**\n * Parameter-mapping resolver for HTTP API v2 service integrations\n * (`IntegrationSubtype` set, no Lambda).\n *\n * AWS API Gateway HTTP APIs let you wire a route directly to an AWS\n * SDK call via `RequestParameters` — a flat map whose KEY is the SDK\n * input parameter name (`QueueUrl`, `MessageBody`, `Source`, ...) and\n * whose VALUE is either a literal string or one of the dollar-prefixed\n * \"selection expression\" forms documented at\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html\n *\n * Supported value forms (all bare and `${...}`-wrapped variants):\n * - `$request.header.<name>` — case-insensitive header lookup; multi-values comma-joined\n * - `$request.querystring.<key>` — case-SENSITIVE; multi-values comma-joined\n * - `$request.path.<param>` — path parameter from `{param}` route patterns\n * - `$request.path` — full request path (without stage)\n * - `$request.body` — entire request body as a string\n * - `$request.body.<jsonpath>` — JSONPath against the parsed JSON body\n * (recursive descent `..` and filter `?()` are NOT supported)\n * - `$context.<key>` — supported context variables\n * (requestId / accountId / domainName / identity.sourceIp / etc.)\n * - `$stageVariables.<key>` — values from the route's selected stage\n *\n * `${X} ${Y}` interpolation: any string containing `${...}` is treated\n * as a template literal — each placeholder is resolved independently\n * and the surrounding literal characters are preserved.\n *\n * Anything that is not a recognized selection expression AND does not\n * contain `${...}` is returned as a literal string.\n *\n * This module is **pure-functional and async-free** — every input is\n * derived from the HTTP request snapshot, route match, and the\n * pre-discovered route state. SDK invocation happens in the dispatcher\n * (see `httpv2-service-integration.ts`).\n *\n * Unresolved references (e.g. `$request.querystring.url` against a\n * request with no `url` parameter) resolve to the empty string —\n * matches deployed API Gateway behavior, which treats absent values\n * as `\"\"` and passes them to the SDK call. The dispatcher's\n * per-subtype validator then surfaces the SDK's typed rejection.\n */\n\nimport { stringifyValue } from '../utils/stringify.js';\n\n/**\n * Snapshot of the HTTP request used during parameter resolution. The\n * server already collected these on the request hot path; we pass them\n * around as a frozen bag so resolution is pure-functional.\n */\nexport interface RequestParameterContext {\n /** Lowercased headers; multi-value headers joined by `, ` per AWS docs. */\n headers: Readonly<Record<string, string>>;\n /** Raw query string parameters; case-sensitive keys; multi-value joined by `,`. */\n queryString: Readonly<Record<string, string>>;\n /** Path parameters extracted from `{param}` route patterns; values are URL-decoded. */\n pathParameters: Readonly<Record<string, string>>;\n /** Request path (without the stage prefix). */\n requestPath: string;\n /** Raw request body string (best-effort UTF-8 decode). */\n body: string;\n /** AWS context variables; only `.` and `_` allowed in keys per AWS docs. */\n context: Readonly<Record<string, string>>;\n /** Stage variables from the matched route's selected Stage. */\n stageVariables: Readonly<Record<string, string>>;\n /**\n * Authorizer verdict surfaced under `$context.authorizer.X` (closes\n * #502). Populated only on auth-protected routes; absent when no\n * authorizer fired. Shape mirrors what `applyAuthorizerOverlay`\n * writes onto the Lambda event:\n *\n * - Lambda authorizers (TOKEN / REQUEST / IAM): `principalId` and\n * context fields land flat at the top level\n * (`$context.authorizer.principalId`, `$context.authorizer.<key>`).\n * - Cognito (REST v1): claims under\n * `$context.authorizer.claims.<key>`.\n * - JWT (HTTP v2): claims under\n * `$context.authorizer.jwt.claims.<key>`, scopes under\n * `$context.authorizer.jwt.scopes`.\n *\n * Nested fields traverse via dot-separated path lookup against this\n * record. Non-string leaves are stringified with `JSON.stringify` so\n * they round-trip into SDK input fields.\n */\n authorizer?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Outcome of resolving the full `RequestParameters` map. Per-parameter\n * resolution never fails (unresolved → `\"\"`), so the only failure mode\n * is a structurally malformed mapping (e.g. a non-string value, or a\n * `${...}` interpolation with an unclosed brace).\n */\nexport type ResolveParametersOutcome =\n | { kind: 'ok'; resolved: Record<string, string> }\n | { kind: 'error'; reason: string };\n\n/**\n * Resolve a `RequestParameters` map (HTTP API v2 service integration\n * shape) against the incoming HTTP request. Keys are passed through\n * verbatim (they identify the SDK input parameter); only the VALUES\n * go through selection-expression resolution.\n */\nexport function resolveServiceIntegrationParameters(\n parameters: Readonly<Record<string, unknown>>,\n ctx: RequestParameterContext\n): ResolveParametersOutcome {\n const resolved: Record<string, string> = {};\n for (const [key, rawValue] of Object.entries(parameters)) {\n if (typeof rawValue !== 'string') {\n return {\n kind: 'error',\n reason: `RequestParameters[${JSON.stringify(key)}] must be a string (got ${typeof rawValue}: ${stringifyValue(\n rawValue\n )}).`,\n };\n }\n try {\n resolved[key] = resolveSelectionExpression(rawValue, ctx);\n } catch (err) {\n return {\n kind: 'error',\n reason: `RequestParameters[${JSON.stringify(key)}]: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n return { kind: 'ok', resolved };\n}\n\n/**\n * Resolve a single selection-expression string. Public for unit\n * testing — production callers use {@link resolveServiceIntegrationParameters}.\n *\n * Three shapes:\n * 1. Pure bare reference (`$request.querystring.url`) — resolved\n * and returned as-is. Whole-string match.\n * 2. Embedded `${...}` interpolation — every placeholder is resolved\n * and concatenated with the surrounding literals.\n * 3. Anything else — returned verbatim as a literal.\n */\nexport function resolveSelectionExpression(input: string, ctx: RequestParameterContext): string {\n // Form 1: pure bare reference. The bare form does NOT allow trailing\n // characters (`$request.path.idX` is one path-param ref, not\n // `$request.path.id` + literal `X`); use `${...}` for that.\n if (input.startsWith('$') && !input.includes('${')) {\n const resolved = resolveSingleReference(input, ctx);\n if (resolved !== undefined) return resolved;\n // Unknown $X.Y form → literal pass-through (matches AWS's\n // permissive behavior — a typo'd `$reqeust.X` lands as the\n // literal string at the SDK call site).\n return input;\n }\n\n // Form 2: ${...} interpolation. Walk the string once, collect\n // placeholders.\n if (input.includes('${')) {\n return interpolate(input, ctx);\n }\n\n // Form 3: literal.\n return input;\n}\n\n/**\n * Walk a `${...}`-templated string and emit the concatenated result.\n * Per AWS docs, `${X}` may contain any of the selection-expression\n * forms recognized in bare form.\n */\nfunction interpolate(input: string, ctx: RequestParameterContext): string {\n let out = '';\n let i = 0;\n while (i < input.length) {\n const next = input.indexOf('${', i);\n if (next === -1) {\n out += input.slice(i);\n break;\n }\n out += input.slice(i, next);\n const end = input.indexOf('}', next + 2);\n if (end === -1) {\n throw new Error(`unclosed '\\${...}' interpolation in selection expression`);\n }\n const inner = input.slice(next + 2, end);\n // Inner expressions DON'T carry the leading `$` — `${request.path.id}`\n // is the canonical shape. Re-add it for the bare resolver.\n const resolved = resolveSingleReference('$' + inner, ctx);\n out += resolved ?? '';\n i = end + 1;\n }\n return out;\n}\n\n/**\n * Resolve one selection-expression reference (without `${...}`\n * wrapping). Returns `undefined` when the reference's PREFIX is not\n * a recognized form (so the bare-form caller can fall through to\n * literal-pass-through); returns `\"\"` when the prefix matched but\n * the referenced datum was absent (AWS-deployed behavior).\n */\nfunction resolveSingleReference(ref: string, ctx: RequestParameterContext): string | undefined {\n if (ref === '$request.body') return ctx.body;\n if (ref === '$request.path') return ctx.requestPath;\n\n if (ref.startsWith('$request.body.')) {\n const path = ref.substring('$request.body.'.length);\n return resolveBodyJsonPath(ctx.body, path);\n }\n if (ref.startsWith('$request.header.')) {\n const name = ref.substring('$request.header.'.length).toLowerCase();\n return ctx.headers[name] ?? '';\n }\n if (ref.startsWith('$request.querystring.')) {\n const key = ref.substring('$request.querystring.'.length);\n return ctx.queryString[key] ?? '';\n }\n if (ref.startsWith('$request.path.')) {\n const key = ref.substring('$request.path.'.length);\n return ctx.pathParameters[key] ?? '';\n }\n if (ref.startsWith('$context.')) {\n const key = ref.substring('$context.'.length);\n // `$context.authorizer.X` resolves against the authorizer verdict\n // (#502). `key` here is `authorizer.X` (or just `authorizer` with\n // no path). Nested fields traverse via dot-separated walk so\n // `$context.authorizer.jwt.claims.sub` lands at the right leaf.\n if (key === 'authorizer' || key.startsWith('authorizer.')) {\n if (!ctx.authorizer) return '';\n if (key === 'authorizer') {\n // Whole authorizer record — stringify (matches AWS-deployed\n // behavior; rare in practice).\n return JSON.stringify(ctx.authorizer);\n }\n return resolveAuthorizerPath(ctx.authorizer, key.substring('authorizer.'.length));\n }\n return ctx.context[key] ?? '';\n }\n if (ref.startsWith('$stageVariables.')) {\n const key = ref.substring('$stageVariables.'.length);\n return ctx.stageVariables[key] ?? '';\n }\n return undefined;\n}\n\n/**\n * Resolve a simple JSON path against the request body. Per AWS docs:\n *\n * - The body is JSON-parsed (best-effort; non-JSON → empty string).\n * - Path segments are `.`-separated. Array indexing via `[N]` is\n * supported.\n * - Recursive descent `..` and filter expressions `?()` are NOT\n * supported and produce `\"\"` (matches AWS rejection at deploy\n * time, but we degrade gracefully at runtime).\n *\n * Non-string leaves are stringified with `JSON.stringify` so they can\n * round-trip into the SDK call's string-typed input fields.\n */\nfunction resolveBodyJsonPath(body: string, path: string): string {\n // Reject recursive descent ($..) and filter expressions ($?()) — AWS\n // docs explicitly call these out as unsupported, so we degrade to ''\n // rather than walking a partial path. The leading-dot check catches\n // `$request.body..x`, which after the substring slice arrives here\n // as `.x` (the first `.` of the `..` got consumed by the static\n // prefix strip in the caller).\n if (path.startsWith('.') || path.includes('..') || path.includes('?(')) return '';\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n return '';\n }\n // Split on `.` while preserving `[N]` index segments.\n const segments = path.split(/\\.|\\[(\\d+)\\]/).filter((s) => s !== undefined && s !== '');\n let cursor: unknown = parsed;\n for (const seg of segments) {\n if (cursor === null || cursor === undefined) return '';\n if (typeof cursor !== 'object') return '';\n if (Array.isArray(cursor)) {\n const idx = Number(seg);\n if (!Number.isInteger(idx)) return '';\n cursor = cursor[idx];\n } else {\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n }\n if (cursor === undefined || cursor === null) return '';\n if (typeof cursor === 'string') return cursor;\n return JSON.stringify(cursor);\n}\n\n/**\n * Walk a dot-separated path against the authorizer verdict map (#502).\n * Used by `$context.authorizer.X` selection expressions on\n * service-integration routes. Mirrors `resolveBodyJsonPath`'s shape:\n * - Empty leaf / undefined → `\"\"`.\n * - String leaf → returned verbatim.\n * - Non-string leaf → `JSON.stringify`'d (matches AWS-deployed\n * behavior; `$context.authorizer.jwt.claims` resolves to the JSON\n * object as a string).\n *\n * Array indexing via `[N]` is supported (rare for authorizer\n * verdicts but cheap to support).\n */\nfunction resolveAuthorizerPath(\n authorizer: Readonly<Record<string, unknown>>,\n path: string\n): string {\n if (path === '') return '';\n const segments = path.split(/\\.|\\[(\\d+)\\]/).filter((s) => s !== undefined && s !== '');\n let cursor: unknown = authorizer;\n for (const seg of segments) {\n if (cursor === null || cursor === undefined) return '';\n if (typeof cursor !== 'object') return '';\n if (Array.isArray(cursor)) {\n const idx = Number(seg);\n if (!Number.isInteger(idx)) return '';\n cursor = cursor[idx];\n } else {\n cursor = (cursor as Record<string, unknown>)[seg];\n }\n }\n if (cursor === undefined || cursor === null) return '';\n if (typeof cursor === 'string') return cursor;\n return JSON.stringify(cursor);\n}\n","import { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { createServer as createHttpsServer } from 'node:https';\nimport type { TLSSocket, PeerCertificate, DetailedPeerCertificate } from 'node:tls';\nimport { readFileSync } from 'node:fs';\nimport { getLogger } from '../utils/logger.js';\nimport { invokeRie, invokeRieStreaming } from './rie-client.js';\nimport {\n applyAuthorizerOverlay,\n buildHttpApiV2Event,\n buildRestV1Event,\n type AuthorizerEventOverlay,\n type HttpRequestSnapshot,\n type MatchedRouteContext,\n} from './api-gateway-event.js';\nimport { translateLambdaResponse } from './api-gateway-response.js';\nimport { matchRoute } from './route-matcher.js';\nimport type { DiscoveredRoute } from './route-discovery.js';\nimport type { ContainerPool } from './container-pool.js';\nimport {\n dispatchAwsLambdaIntegration,\n dispatchHttpIntegration,\n dispatchHttpProxyIntegration,\n dispatchMockIntegration,\n type RestV1IntegrationRequest,\n type RestV1IntegrationOutcome,\n} from './rest-v1-integrations.js';\nimport { randomUUID } from 'node:crypto';\nimport { applyCorsResponseHeaders, matchPreflight, type CorsConfig } from './cors-handler.js';\nimport type { AuthorizerInfo, RouteWithAuth } from './authorizer-resolver.js';\nimport type { AuthorizerCache, CachedAuthorizerResult } from './authorizer-cache.js';\nimport { buildAuthorizerContextShape } from './authorizer-context.js';\nimport {\n buildMethodArn,\n computeRequestIdentityHash,\n evaluateCachedLambdaPolicy,\n invokeRequestAuthorizer,\n invokeTokenAuthorizer,\n} from './lambda-authorizer.js';\nimport { type JwksCache, verifyCognitoJwt, verifyJwtAuthorizer } from './cognito-jwt.js';\nimport { type CredentialsLoader, verifySigV4 } from './sigv4-verify.js';\nimport {\n applyResponseParameters,\n dispatchServiceIntegration,\n type ResponseParameterContext,\n} from './httpv2-service-integration.js';\nimport {\n resolveServiceIntegrationParameters,\n type RequestParameterContext,\n} from './parameter-mapping.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * The user-facing HTTP server for `cdkl start-api`.\n *\n * Wires together:\n * - {@link matchRoute} for routing (3-tier precedence + literal-segment\n * tie-break);\n * - {@link buildHttpApiV2Event} / {@link buildRestV1Event} for event\n * construction;\n * - {@link ContainerPool} for per-Lambda warm container reuse;\n * - {@link translateLambdaResponse} for response translation.\n *\n * PR 8b additions:\n * - Authorizer pass: when the matched route has an attached authorizer,\n * the server invokes it (Lambda TOKEN / REQUEST or Cognito / JWT\n * verify) before forwarding to the route handler. Allow → claims /\n * context propagated into `event.requestContext.authorizer`. Deny →\n * 401 / 403 written directly without invoking the route handler.\n * Caches per {@link AuthorizerCache}'s TTL.\n *\n * PR 8c additions:\n * - CORS preflight interception (HTTP API v2 only) runs BEFORE the\n * authorizer pass — preflight responses do NOT carry credentials per\n * the CORS spec, so they must succeed without an Authorization\n * header.\n * - Hot reload support: `ServerState` is held in a closure cell;\n * `setServerState` swaps it atomically; the `node:http` listener is\n * never reconstructed.\n *\n * Critical: this module does NOT instantiate `live-renderer` or any\n * other `setInterval`-driven thing. The event loop must be free to\n * drain on graceful shutdown so `process.exit(0)` works.\n */\n\n/**\n * Mutable server state read on every incoming request. Hot reload (PR\n * 8c) swaps the entire `ServerState` atomically via the\n * `setServerState` callback returned from `startApiServer`, so the\n * server keeps serving against the new template without restarting the\n * `node:http` listener (and without dropping in-flight requests — they\n * run against the old state until `pool.dispose()` returns).\n *\n * Each field corresponds to one piece of \"what the server is serving\":\n *\n * - `routes` — the discovered routes with their attached authorizer\n * info (output of `attachAuthorizers(discoverRoutes(...))`). Routes\n * without an authorizer carry `authorizer: undefined`.\n * - `pool` — the per-Lambda container pool. Hot reload may swap pools\n * when the set of reachable Lambdas changes; the old pool's\n * `dispose()` runs in the orchestrator after the swap.\n * - `corsConfigByApiId` — `apiLogicalId → CorsConfig` map. Routes\n * whose `apiLogicalId` is in this map participate in OPTIONS\n * preflight interception.\n */\nexport interface ServerState {\n routes: readonly RouteWithAuth[];\n pool: ContainerPool;\n corsConfigByApiId: Map<string, CorsConfig>;\n}\n\nexport interface StartApiServerOptions {\n /** Initial state. The server reads this on the first request. */\n state: ServerState;\n /** RIE invoke timeout in ms. Default `2 * max(timeoutSec) * 1000`, floor 30s. */\n rieTimeoutMs: number;\n /** Bind host (default `127.0.0.1`). */\n host: string;\n /** Bind port (or 0 for auto-allocation). */\n port: number;\n /** Authorizer-result cache (PR 8b). When omitted, every request re-invokes. */\n authorizerCache?: AuthorizerCache;\n /** JWKS cache (PR 8b). Required when any route has a Cognito / JWT authorizer. */\n jwksCache?: JwksCache;\n /**\n * Per-server-lifecycle Set of JWKS URLs we have already emitted a\n * pass-through warn line for (PR 8b post-review fix). Constructed once\n * by the caller (`local-start-api.ts`) and threaded through to every\n * request so the warn fires at most ONCE per JWKS URL per server.\n * When omitted, the verifier no-ops the warn (used in tests where the\n * server is not started through the CLI bootstrap).\n */\n jwksWarnedUrls?: Set<string>;\n /**\n * Optional mTLS configuration. When set, the server uses\n * `https.createServer({requestCert: true, rejectUnauthorized: true,\n * ca, cert, key})` instead of plain `http.createServer`, and only\n * accepts connections whose client certificate chains back to the CA\n * bundle. The verified peer certificate is surfaced on the event under\n * `requestContext.identity.clientCert` (REST v1) / `requestContext.authentication.clientCert`\n * (HTTP v2). When unset, the server uses plain HTTP (the pre-PR\n * behavior).\n *\n * mTLS is enabled in `cdkl start-api` only when ALL THREE\n * `--mtls-truststore`, `--mtls-cert`, and `--mtls-key` flags are set;\n * partial flag sets are rejected at the CLI parse layer before this\n * function is called.\n */\n mtls?: MtlsServerConfig;\n /**\n * Local-credentials loader for SigV4 signature verification on\n * `AuthorizationType: 'AWS_IAM'` routes (#447). Required when any\n * route uses AWS_IAM; ignored otherwise. The loader is cached at the\n * loader layer so the credential chain is hit at most once per server\n * lifecycle.\n */\n sigV4CredentialsLoader?: CredentialsLoader;\n /**\n * Per-server-lifecycle Set of `Credential=` access-key-ids we have\n * already emitted a warn-and-pass line for (foreign-identity SigV4\n * requests cannot be verified against the dev's local key). Same\n * dedup pattern as `jwksWarnedUrls`.\n */\n sigV4WarnedForeignIds?: Set<string>;\n /**\n * Opt-in: DENY SigV4 requests that cannot be verified (foreign\n * access-key-id OR local-credentials-load failure) instead of the default\n * warn-and-pass. DEFAULT off (warn-and-pass) — local execution targets app\n * logic / ergonomics, not an auth boundary cdk-local cannot fully emulate;\n * the `--strict-sigv4` CLI flag flips this on. OAC-fronted Function URLs\n * always warn-and-pass regardless of this setting.\n */\n sigV4Strict?: boolean;\n /**\n * Default AWS region for HTTP API v2 service integrations (#458).\n * Resolved by the CLI from `--region` / `AWS_REGION` / profile at\n * boot. Per-route `RequestParameters.Region` overrides this on a\n * per-request basis (matches AWS API Gateway behavior).\n *\n * Required when any route has `serviceIntegration` set; unused\n * otherwise. The dispatcher rejects calls with a 400 error when\n * both this and the per-request override are empty.\n */\n defaultRegion?: string;\n /**\n * Optional pre-dispatch hook (#462 — WebSocket support). Runs against\n * EVERY incoming HTTP request BEFORE CORS / route-matching /\n * authorizer / dispatch. Returns `true` to short-circuit the rest of\n * the pipeline (the hook has written the full response itself),\n * `false` to continue normal HTTP dispatch.\n *\n * Wired by `cdkl start-api` on a server hosting a WebSocket\n * API to intercept `POST/GET/DELETE /@connections/<id>` calls so the\n * AWS SDK's `apigatewaymanagementapi:PostToConnection` (issued from\n * inside a Lambda container via `AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI`\n * override) reaches the WebSocket's connection registry.\n *\n * The hook is async-safe — `handleRequest` awaits it and treats any\n * throw as a 502.\n */\n preDispatch?: (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;\n}\n\n/**\n * Server-side mTLS configuration. Carries the PEM-encoded materials the\n * TLS handshake needs:\n *\n * - `caPem`: trust-store bundle. Client certs must chain to one of\n * these CAs for the handshake to succeed. Node's `tls` module does\n * the chain verification at handshake time.\n * - `certPem`: server certificate (self-signed is fine for local dev\n * — clients should use `--insecure` or trust the cert).\n * - `keyPem`: server private key matching `certPem`.\n */\nexport interface MtlsServerConfig {\n caPem: Buffer;\n certPem: Buffer;\n keyPem: Buffer;\n}\n\nexport interface StartedApiServer {\n /** The actual port the server is listening on (after auto-alloc). */\n port: number;\n /** The host the server is bound to. */\n host: string;\n /**\n * `'https'` when mTLS is active, otherwise `'http'`. Used by the\n * CLI's listening banner so users see the right URL scheme.\n */\n scheme: 'http' | 'https';\n /** Underlying Node http.Server or https.Server (for `close()` plumbing). */\n server: Server;\n /**\n * Drain in-flight requests, close the server. Resolves once the\n * server has flushed every connection. Safe to call multiple times.\n */\n close: () => Promise<void>;\n /**\n * Atomically swap the server's state. Hot reload (PR 8c) calls this\n * with the new `routes` / `pool` / `corsConfigByApiId` after re-synth\n * + re-discovery completes. Returns the previous state so the caller\n * can `pool.dispose()` it in the background once in-flight requests\n * drain.\n */\n setServerState: (next: ServerState) => ServerState;\n /** Read the current state (for tests / orchestrator diagnostics). */\n getServerState: () => ServerState;\n}\n\n/**\n * Bind a server and start serving requests. Resolves once the server\n * is listening (after which the caller is expected to print\n * `Server listening on http://<host>:<port>` per D8.4).\n */\nexport async function startApiServer(opts: StartApiServerOptions): Promise<StartedApiServer> {\n const logger = getLogger().child('start-api');\n // The state is held in a closure cell so request handlers always read\n // the latest value. Hot reload mutates `currentState` via\n // `setServerState`; the server itself is never reconstructed.\n let currentState: ServerState = opts.state;\n\n // Branch on mTLS: when configured, use `https.createServer` with\n // `requestCert: true` + `rejectUnauthorized: true` so the TLS\n // handshake enforces the client-cert chain check against the CA\n // bundle. Node's `tls` module rejects unknown-CA / self-signed /\n // missing client certs at handshake time — no per-request code path\n // is needed. The verified peer cert is exposed via the TLS socket\n // and surfaced on the event under `requestContext.identity.clientCert`.\n const requestHandler = (req: IncomingMessage, res: ServerResponse) => {\n handleRequest(req, res, currentState, opts).catch((err) => {\n logger.error(\n `Unhandled request error: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n if (!res.headersSent) {\n writeError(res, 502);\n }\n });\n };\n const server: Server = opts.mtls\n ? (createHttpsServer(\n {\n requestCert: true,\n rejectUnauthorized: true,\n ca: opts.mtls.caPem,\n cert: opts.mtls.certPem,\n key: opts.mtls.keyPem,\n },\n requestHandler\n ) as unknown as Server)\n : createServer(requestHandler);\n const scheme: 'http' | 'https' = opts.mtls ? 'https' : 'http';\n\n // Disable Nagle's algorithm for snappier curl interactions; trivial\n // win on a local server.\n server.on('connection', (socket) => {\n socket.setNoDelay(true);\n });\n\n const { actualPort, actualHost } = await new Promise<{ actualPort: number; actualHost: string }>(\n (resolveListen, rejectListen) => {\n server.once('error', rejectListen);\n server.listen(opts.port, opts.host, () => {\n const addr = server.address();\n if (addr === null || typeof addr === 'string') {\n rejectListen(new Error('Could not determine listening address'));\n return;\n }\n resolveListen({ actualPort: addr.port, actualHost: opts.host });\n });\n }\n );\n\n let closed = false;\n return {\n port: actualPort,\n host: actualHost,\n scheme,\n server,\n close: async (): Promise<void> => {\n if (closed) return;\n closed = true;\n await new Promise<void>((resolveClose) => {\n server.close(() => resolveClose());\n // Force-close keep-alive sockets so close() actually returns.\n server.closeAllConnections?.();\n });\n },\n setServerState: (next: ServerState): ServerState => {\n const prev = currentState;\n currentState = next;\n return prev;\n },\n getServerState: (): ServerState => currentState,\n };\n}\n\n/**\n * Handle a single incoming HTTP request: read body, optionally\n * intercept CORS preflight, match route, invoke authorizer (if any),\n * build event, acquire container, invoke RIE, release container,\n * translate response, write response. Errors at any stage become a 502\n * response.\n *\n * Order of phases (load-bearing):\n * 1. CORS preflight interception (PR 8c). OPTIONS requests on an HTTP\n * API v2 with a `CorsConfiguration` short-circuit here without\n * touching the authorizer pass — preflight responses MUST succeed\n * without an Authorization header per the CORS spec.\n * 2. Route match. 404s exit before the authorizer pass.\n * 3. Authorizer pass (PR 8b). Deny → 401 / 403 without invoking the\n * route handler.\n * 4. Build event, acquire container, invoke RIE, translate response.\n */\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n state: ServerState,\n opts: StartApiServerOptions\n): Promise<void> {\n const logger = getLogger().child('start-api');\n\n // Pre-dispatch hook (#462 — WebSocket support). Runs BEFORE body\n // read so the hook owns the entire request lifecycle for paths it\n // claims (the `@connections` POST consumes the body itself, and a\n // double-read would corrupt the stream). The hook returns `true`\n // when it handled the request → short-circuit; `false` to fall\n // through to the normal HTTP pipeline.\n if (opts.preDispatch) {\n try {\n const handled = await opts.preDispatch(req, res);\n if (handled) return;\n } catch (err) {\n logger.error(\n `preDispatch hook threw: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n if (!res.headersSent) writeError(res, 502);\n return;\n }\n }\n\n // Read the request body (eager, all-in-memory). Local-only — large\n // bodies are not a concern in v1.\n const bodyBuf = await readBody(req);\n\n const rawUrl = req.url ?? '/';\n const method = (req.method ?? 'GET').toUpperCase();\n\n const requestPath = rawUrl.split('?')[0] ?? '/';\n\n // PR 8c: CORS preflight interception. We attempt to find a route the\n // OPTIONS request matches as if it were the actual method (we look at\n // `Access-Control-Request-Method` per the CORS spec); when found AND\n // the API has a CORS config AND no explicit OPTIONS route is\n // registered, we respond with the canonical preflight headers.\n // Preflight runs BEFORE the authorizer pass — preflight requests\n // never carry credentials per the CORS spec.\n const preflightHandled =\n method === 'OPTIONS' ? maybeHandleCorsPreflight(req, res, requestPath, state) : false;\n if (preflightHandled) return;\n\n const flatRoutes = state.routes.map((r) => r.route);\n const match = matchRoute(method, requestPath, flatRoutes);\n if (!match) {\n writeError(res, 404, '{\"message\":\"Not Found\"}');\n return;\n }\n\n // Issue #652: apply CORS headers to ACTUAL responses (preflight is\n // handled above by `maybeHandleCorsPreflight`). The browser blocks\n // 2xx / 4xx / 5xx response bodies from JS unless\n // `Access-Control-Allow-Origin` is set on the actual response too.\n // Closure captures the route + state + request Origin so every\n // response-write call site in this handler can apply CORS without\n // re-deriving them. Idempotent: safe to call multiple times (a later\n // call overwrites the headers from an earlier one).\n const requestOrigin = pickFirstHeaderValue(collectHeaders(req), 'origin') ?? undefined;\n const applyCors = (): void => {\n applyCorsResponseHeaders(res, match.route.apiLogicalId, state.corsConfigByApiId, requestOrigin);\n };\n\n // Synthetic CORS preflight (REST v1 MOCK preflight): respond with\n // the captured status + headers directly, no Lambda invocation. Runs\n // BEFORE the authorizer pass — preflight requests do not carry\n // credentials per the CORS spec.\n if (match.route.mockCors) {\n writeMockCorsPreflight(res, match.route.mockCors);\n return;\n }\n\n // Unsupported route — discovered with enough structure to match\n // (method + path), but no Lambda dispatch is possible. Surface the\n // reason as HTTP 501 so the user sees exactly what went wrong WHEN\n // they hit the route, while the rest of the API surface stays up.\n if (match.route.unsupported) {\n applyCors();\n writeNotImplemented(res, match.route.unsupported.reason);\n return;\n }\n\n // Find the authorizer attached to the matched route (if any).\n // Includes service-integration routes (#502): closes the gap left by\n // PR #500 where the SDK dispatcher ran BEFORE the authorizer pass and\n // every auth-protected service-integration route silently bypassed\n // authentication. Post-fix the authorizer pass runs FIRST, regardless\n // of whether the route is a Lambda or a service integration.\n const matchedEntry = state.routes.find(\n (r) => r.route.declaredAt === match.route.declaredAt && r.route.method === match.route.method\n );\n const authorizer = matchedEntry?.authorizer;\n\n // Extract the verified client certificate when mTLS is active. By the\n // time `handleRequest` runs, the TLS handshake has already passed\n // (Node's `tls` module rejects unknown-CA / self-signed / missing\n // client certs BEFORE the request reaches us — see\n // `rejectUnauthorized: true` on the https.createServer above), so any\n // cert we see here is structurally valid against the supplied CA\n // bundle. We surface it on the event under\n // `requestContext.identity.clientCert` (REST v1 shape) so handlers\n // can extract identity claims from the cert without re-doing the\n // chain check.\n const clientCert = opts.mtls ? extractClientCert(req) : undefined;\n const snapshot: HttpRequestSnapshot = {\n method,\n rawUrl,\n headers: collectHeaders(req),\n body: bodyBuf,\n ...(req.socket.remoteAddress !== undefined && { sourceIp: req.socket.remoteAddress }),\n ...(clientCert && { clientCert }),\n };\n const matchCtx: MatchedRouteContext = {\n route: match.route,\n pathParameters: match.pathParameters,\n matchedPath: requestPath,\n };\n\n let baseEvent =\n match.route.apiVersion === 'v1'\n ? buildRestV1Event(snapshot, matchCtx)\n : buildHttpApiV2Event(snapshot, matchCtx);\n\n // Authorizer pass. Runs BEFORE service-integration dispatch (#502)\n // and BEFORE container acquire, so a deny aborts the request without\n // any AWS-side side effects on service-integration routes — symmetric\n // with Lambda routes and matches AWS-deployed API Gateway behavior.\n let authResult: CachedAuthorizerResult | undefined;\n if (authorizer) {\n let outcome: AuthorizerOutcome;\n try {\n outcome = await runAuthorizerPass(\n authorizer,\n snapshot,\n matchCtx,\n state,\n opts,\n baseEvent['requestContext'] as Record<string, unknown>\n );\n } catch (err) {\n logger.error(\n `Authorizer ${authorizer.logicalId} threw for ${match.route.declaredAt}: ${err instanceof Error ? err.message : String(err)}`\n );\n // Authorizer error is treated as policy-deny (403 on REST v1, 401\n // on HTTP v2, 403 on Function URL AWS_IAM) — matches deployed\n // behavior on Lambda authorizer exceptions / SigV4 verification\n // failures.\n applyCors();\n writeAuthRejection(res, match.route.apiVersion, 'policy-deny', authorizer.kind);\n return;\n }\n if (!outcome.result.allow) {\n applyCors();\n writeAuthRejection(\n res,\n match.route.apiVersion,\n outcome.denyKind ?? 'policy-deny',\n authorizer.kind\n );\n return;\n }\n authResult = outcome.result;\n const overlay = buildOverlay(authorizer, authResult, match.route.apiVersion);\n if (overlay) {\n baseEvent = applyAuthorizerOverlay(baseEvent, overlay);\n }\n }\n\n // HTTP API v2 service integration (#458 / #502): dispatch to the SDK\n // via the per-subtype adapter table AFTER the authorizer pass — an\n // auth-protected SQS / EventBridge / etc. route rejects unauthenticated\n // requests the same way Lambda routes do. The dispatcher receives the\n // authorizer's context (claims / principalId / Lambda context) via\n // `RequestParameterContext.authorizer` so `$context.authorizer.X`\n // selection expressions resolve correctly.\n if (match.route.serviceIntegration) {\n applyCors();\n await handleServiceIntegrationRequest(req, res, match, bodyBuf, opts, authorizer, authResult);\n return;\n }\n\n // REST v1 non-AWS_PROXY dispatch (#457) — runs AFTER the authorizer\n // pass so authorizer-protected MOCK / HTTP / AWS Lambda non-proxy\n // routes still reject unauthenticated calls. The dispatchers in\n // `src/local/rest-v1-integrations.ts` apply VTL templates and shape\n // the response according to `IntegrationResponses[]`.\n if (match.route.restV1Integration) {\n try {\n const outcome = await dispatchRestV1Integration(\n match.route.restV1Integration,\n snapshot,\n matchCtx,\n state,\n opts\n );\n applyCors();\n writeIntegrationOutcome(res, outcome);\n } catch (err) {\n logger.error(\n `REST v1 ${match.route.restV1Integration.kind} dispatch failed for ${match.route.declaredAt}: ${err instanceof Error ? err.message : String(err)}`\n );\n if (!res.headersSent) {\n applyCors();\n writeError(res, 502);\n } else {\n res.end();\n }\n }\n return;\n }\n\n let handle;\n try {\n handle = await state.pool.acquire(match.route.lambdaLogicalId);\n } catch (err) {\n logger.error(\n `Failed to acquire container for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`\n );\n applyCors();\n writeError(res, 502);\n return;\n }\n\n // Function URL routes with InvokeMode: RESPONSE_STREAM dispatch via\n // the RIE streaming protocol (issue #467). The streaming Lambda's\n // response is a JSON prelude carrying status + headers, an 8-NULL-byte\n // separator, and then raw body chunks the handler streams. cdk-local writes\n // the prelude to `res.writeHead(...)` and pipes the chunks via\n // `Transfer-Encoding: chunked` (Node's default when no Content-Length\n // is set).\n if (match.route.invokeMode === 'RESPONSE_STREAM') {\n let streamResult: import('./rie-client.js').StreamingInvokeResult | undefined;\n try {\n streamResult = await invokeRieStreaming(\n handle.containerHost,\n handle.hostPort,\n baseEvent,\n opts.rieTimeoutMs\n );\n try {\n applyCors();\n writeStreamingResponse(res, streamResult, () => state.pool.release(handle));\n } catch (writeErr) {\n // `writeStreamingResponse` threw synchronously — typically from\n // `res.writeHead(...)` rejecting a malformed header value before\n // any body bytes have been piped. The body Readable from\n // `invokeRieStreaming` has no `'error'` / `'close'` consumer\n // installed yet (those listeners are attached AFTER `writeHead`\n // inside `writeStreamingResponse`), so the IIFE in\n // `invokeRieStreaming` would keep pushing chunks into an orphan\n // Readable forever. Destroy it explicitly to release the underlying\n // fetch reader, then re-throw so the outer catch surfaces 502 and\n // releases the pool entry.\n //\n // The no-op `'error'` listener is load-bearing: Node emits the\n // destroy reason as an `'error'` event on the Readable, and an\n // unhandled `'error'` would surface as an uncaught exception\n // since this branch is reached before `body.pipe(res)` would\n // have installed its own internal error handler.\n streamResult.body.on('error', () => {\n /* swallow — the original `writeErr` is what the caller sees */\n });\n streamResult.body.destroy(\n writeErr instanceof Error ? writeErr : new Error(String(writeErr))\n );\n throw writeErr;\n }\n // writeStreamingResponse owns the pool release because the body\n // is piped asynchronously — the response is not \"done\" when this\n // function returns.\n return;\n } catch (err) {\n logger.error(\n `RIE streaming invoke failed for ${match.route.lambdaLogicalId}: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n if (!res.headersSent) {\n applyCors();\n writeError(res, 502);\n } else {\n res.end();\n }\n state.pool.release(handle);\n return;\n }\n }\n\n try {\n const invokeResult = await invokeRie(\n handle.containerHost,\n handle.hostPort,\n baseEvent,\n opts.rieTimeoutMs\n );\n\n const translated = translateLambdaResponse(invokeResult.payload, match.route.apiVersion);\n res.statusCode = translated.statusCode;\n for (const [name, value] of Object.entries(translated.headers)) {\n res.setHeader(name, value);\n }\n if (translated.cookies.length > 0) {\n // Multiple Set-Cookie headers — Node's setHeader accepts an array.\n res.setHeader('set-cookie', translated.cookies);\n }\n applyCors();\n res.end(translated.body);\n } catch (err) {\n logger.error(\n `RIE invoke failed for ${match.route.lambdaLogicalId}: ${err instanceof Error ? err.message : String(err)}`\n );\n if (!res.headersSent) {\n applyCors();\n writeError(res, 502);\n } else {\n res.end();\n }\n } finally {\n state.pool.release(handle);\n }\n}\n\n/**\n * Pipe a streaming RIE response into an `http.ServerResponse`. The\n * prelude's status + headers are written via `res.writeHead(...)`; the\n * body Readable is `pipe`'d through to the response so Node's\n * `Transfer-Encoding: chunked` machinery handles backpressure +\n * chunked framing automatically.\n *\n * `releasePool` runs in a `finally`-equivalent path so the warm\n * container is returned to the pool whether the stream ends cleanly\n * (`'end'` event) or errors mid-body (`'error'` event). Errors after\n * the prelude has been written can no longer be reported as HTTP\n * status — the stream is destroyed and the connection aborts.\n *\n * Cookies in the prelude are emitted as multiple `Set-Cookie` headers\n * (HTTP API v2 semantics — matching the buffered path's behavior).\n *\n * **Conflicting headers stripped**: `Content-Length` and\n * `Transfer-Encoding` (case-insensitive) are removed from the prelude\n * before `res.writeHead(...)` — some handlers set these defensively,\n * but doing so either crashes Node (Content-Length doesn't match the\n * actual bytes that arrive on the chunked body) or produces a\n * corrupted Content-Length-but-actually-chunked wire response. Node\n * automatically emits `Transfer-Encoding: chunked` when no\n * Content-Length is set, which is what streaming Function URLs\n * always want.\n */\nfunction writeStreamingResponse(\n res: ServerResponse,\n result: import('./rie-client.js').StreamingInvokeResult,\n releasePool: () => void\n): void {\n const logger = getLogger().child('start-api');\n const { prelude, body } = result;\n\n // Write headers + status atomically so Node knows we're not setting\n // Content-Length and switches to chunked encoding automatically.\n // Cookies are stitched in via the array-form Set-Cookie header\n // (Node's writeHead accepts string-or-array values).\n //\n // Strip `Content-Length` / `Transfer-Encoding` (case-insensitive) so\n // a handler that set them defensively doesn't break the chunked-\n // encoding contract Node enforces automatically.\n const headersOut: Record<string, string | string[]> = {};\n for (const [key, value] of Object.entries(prelude.headers)) {\n const lower = key.toLowerCase();\n if (lower === 'content-length' || lower === 'transfer-encoding') continue;\n headersOut[key] = value;\n }\n if (prelude.cookies && prelude.cookies.length > 0) {\n headersOut['set-cookie'] = prelude.cookies;\n }\n res.writeHead(prelude.statusCode, headersOut);\n\n let released = false;\n const releaseOnce = () => {\n if (released) return;\n released = true;\n releasePool();\n };\n\n body.on('error', (err) => {\n logger.error(\n `Streaming Lambda response body errored mid-stream: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n // Headers already on the wire — best we can do is destroy the\n // socket so the client sees a truncated response.\n res.destroy(err);\n releaseOnce();\n });\n res.on('close', releaseOnce);\n body.pipe(res);\n}\n\n/**\n * Dispatch a REST v1 non-AWS_PROXY integration to the matching handler.\n * Built once per request from the matched route + request snapshot.\n *\n * Returns a {@link RestV1IntegrationOutcome} — the caller writes it\n * onto the `ServerResponse` via {@link writeIntegrationOutcome}.\n */\nasync function dispatchRestV1Integration(\n integration: NonNullable<DiscoveredRoute['restV1Integration']>,\n snapshot: HttpRequestSnapshot,\n matchCtx: MatchedRouteContext,\n state: ServerState,\n opts: StartApiServerOptions\n): Promise<RestV1IntegrationOutcome> {\n const headers = lowercaseSingularHeaders(snapshot.headers);\n const querystring = parseQueryStringSingular(snapshot.rawUrl);\n const sourceIp = snapshot.sourceIp ?? '127.0.0.1';\n const userAgent = headers['user-agent'] ?? '';\n\n const req: RestV1IntegrationRequest = {\n method: snapshot.method.toUpperCase(),\n matchedPath: matchCtx.matchedPath,\n pathParameters: matchCtx.pathParameters,\n querystring,\n headers,\n body: snapshot.body,\n sourceIp,\n userAgent,\n stage: matchCtx.route.stage,\n resourcePath: matchCtx.route.pathPattern,\n requestId: randomUUID(),\n };\n\n const deps = { pool: state.pool, rieTimeoutMs: opts.rieTimeoutMs };\n\n switch (integration.kind) {\n case 'mock':\n return dispatchMockIntegration(integration, req);\n case 'http-proxy':\n return await dispatchHttpProxyIntegration(integration, req, deps);\n case 'http':\n return await dispatchHttpIntegration(integration, req, deps);\n case 'aws-lambda':\n return await dispatchAwsLambdaIntegration(integration, req, deps);\n }\n}\n\n/**\n * Write a {@link RestV1IntegrationOutcome} to the HTTP response.\n */\nfunction writeIntegrationOutcome(res: ServerResponse, outcome: RestV1IntegrationOutcome): void {\n res.statusCode = outcome.statusCode;\n for (const [name, value] of Object.entries(outcome.headers)) {\n res.setHeader(name, value);\n }\n res.end(outcome.body);\n}\n\n/**\n * Attempt CORS preflight interception. Returns `true` when the\n * preflight response was written (caller must NOT continue to route\n * dispatch); `false` when no preflight match (caller falls through to\n * normal request dispatch — typically a 404 / user OPTIONS handler).\n *\n * Match conditions (all must hold):\n * 1. The request's `Access-Control-Request-Method` header points at a\n * route on an HTTP API v2 (`apiLogicalId` set, route's\n * `apiVersion === 'v2'`).\n * 2. That API has a CORS config in `state.corsConfigByApiId`.\n * 3. There is NO explicit OPTIONS route registered for `requestPath`\n * — the user's Lambda owns the OPTIONS surface in that case.\n * 4. The request's Origin / Method / Headers all satisfy the CORS\n * config (delegated to {@link matchPreflight}).\n */\nfunction maybeHandleCorsPreflight(\n req: IncomingMessage,\n res: ServerResponse,\n requestPath: string,\n state: ServerState\n): boolean {\n if (state.corsConfigByApiId.size === 0) return false;\n\n const headers = collectHeaders(req);\n const requestedMethodHeader = pickFirstHeaderValue(headers, 'access-control-request-method');\n if (!requestedMethodHeader) return false;\n\n // Find the route the requested method would have hit. We DON'T need\n // to be perfectly precise here — the existence of a matching route on\n // the HTTP API v2 is enough to know \"this is a route we own; emit\n // preflight\". If no route matches, we let the request fall through to\n // 404.\n const flatRoutes = state.routes.map((r) => r.route);\n const surrogateMatch = matchRoute(requestedMethodHeader, requestPath, flatRoutes);\n if (!surrogateMatch) return false;\n const route = surrogateMatch.route;\n if (route.apiVersion !== 'v2' || !route.apiLogicalId) return false;\n\n // Skip when the user has an explicit OPTIONS method registered ON THE\n // SAME API (their Lambda owns it). The `apiLogicalId` filter is\n // load-bearing — without it, an explicit OPTIONS route on Stack B's\n // REST v1 API at the same path would suppress preflight on Stack A's\n // HTTP API v2 (the bug the PR review caught). `method === 'OPTIONS'`\n // is the only signal of explicit user intent — `ANY` is a catch-all\n // and doesn't represent CORS-handling intent.\n const surrogateApiId = route.apiLogicalId;\n const explicitOptionsRoute = flatRoutes.find(\n (r) =>\n r.apiLogicalId === surrogateApiId &&\n r.method.toUpperCase() === 'OPTIONS' &&\n pathPatternMatchesPath(r.pathPattern, requestPath)\n );\n if (explicitOptionsRoute) return false;\n\n const cors = state.corsConfigByApiId.get(surrogateApiId);\n if (!cors) return false;\n\n const preflight = matchPreflight({ method: 'OPTIONS', headers }, cors);\n if (!preflight) return false;\n\n res.statusCode = preflight.statusCode;\n for (const [name, value] of Object.entries(preflight.headers)) {\n res.setHeader(name, value);\n }\n res.end();\n return true;\n}\n\n/**\n * Compatibility helper: AWS API Gateway path patterns use `{name}` /\n * `{name+}` placeholders. We need a binary \"does this pattern match\n * the request path\" check (used to detect explicit OPTIONS routes).\n *\n * Reuses the same segment-walk logic as the route matcher, but\n * collapsed to a boolean — copying the rules avoids a circular import\n * back into `route-matcher.ts`'s richer `matchRoute` API.\n */\nfunction pathPatternMatchesPath(pattern: string, requestPath: string): boolean {\n if (pattern === '$default') return true;\n const requestSegments = requestPath.split('/').filter((s) => s.length > 0);\n const patternSegments = pattern.split('/').filter((s) => s.length > 0);\n // Greedy `{proxy+}` consumes every remaining segment.\n if (patternSegments.length > 0) {\n const tail = patternSegments[patternSegments.length - 1]!;\n if (/^\\{[^/{}]+\\+\\}$/.test(tail)) {\n const fixed = patternSegments.length - 1;\n if (requestSegments.length < fixed) return false;\n for (let i = 0; i < fixed; i++) {\n const ps = patternSegments[i]!;\n const rs = requestSegments[i]!;\n if (/^\\{[^/{}+]+\\}$/.test(ps)) continue;\n if (ps !== rs) return false;\n }\n return true;\n }\n }\n if (patternSegments.length !== requestSegments.length) return false;\n for (let i = 0; i < patternSegments.length; i++) {\n const ps = patternSegments[i]!;\n const rs = requestSegments[i]!;\n if (/^\\{[^/{}+]+\\}$/.test(ps)) continue;\n if (ps !== rs) return false;\n }\n return true;\n}\n\n/**\n * Pick the first value for a header (case-insensitive). Returns `null`\n * when the header isn't present. Used by the CORS preflight matcher\n * which only cares about `access-control-request-method` /\n * `access-control-request-headers`.\n */\nfunction pickFirstHeaderValue(headers: Record<string, string[]>, name: string): string | null {\n const lower = name.toLowerCase();\n for (const [k, v] of Object.entries(headers)) {\n if (k.toLowerCase() === lower && v.length > 0) return v[0]!;\n }\n return null;\n}\n\n/**\n * Outcome of an authorizer pass. The HTTP server uses `denyKind` to\n * differentiate REST v1 missing-identity (401) from policy-deny (403);\n * HTTP v2 collapses both to 401.\n */\ninterface AuthorizerOutcome {\n result: CachedAuthorizerResult;\n /**\n * `'missing-identity'` when the request lacks the configured identity\n * source (REST v1 → 401); `'policy-deny'` when the authorizer ran and\n * denied (REST v1 → 403). Unset on Allow outcomes.\n */\n denyKind?: 'missing-identity' | 'policy-deny';\n}\n\n/**\n * Run the authorizer (cache hit or fresh invocation) and return the\n * verdict + denyKind. Treats `result.allow === true` as the only happy\n * path; the http-server gates route forwarding on this.\n *\n * **Cache semantics (PR #237 review fixes)**:\n * - The cache is keyed by `(authorizerLogicalId, identityHash)` and\n * stores the authorizer's verdict shape (principalId, policyDocument,\n * context) NOT the per-request `Resource`-evaluated allow/deny. On\n * every Lambda-authorizer cache hit we re-run `resourceMatches`\n * against the current request's methodArn so a narrow-Resource\n * policy doesn't leak across routes.\n * - For REQUEST authorizers we pre-compute the identity hash from\n * the request snapshot BEFORE invoking the Lambda and consult the\n * cache first; only a cache miss triggers `invokeRequestAuthorizer`.\n * (Pre-fix: every request invoked, then checked cache.)\n */\nasync function runAuthorizerPass(\n authorizer: AuthorizerInfo,\n snapshot: HttpRequestSnapshot,\n matchCtx: MatchedRouteContext,\n state: ServerState,\n opts: StartApiServerOptions,\n requestContextV2: Record<string, unknown>\n): Promise<AuthorizerOutcome> {\n // Build the snapshot the authorizer-invoker consumes. We use the v2\n // header+query maps for both versions because the local server canon-\n // icalizes those upstream; the route-event builder is the only place\n // that re-emits the v1 multi-value shape, and the authorizer event\n // builders re-derive multiValueHeaders from this single map.\n const headers = lowercaseSingularHeaders(snapshot.headers);\n const queryStringParameters = parseQueryStringSingular(snapshot.rawUrl);\n const sourceIp = pickSourceIp(matchCtx.route.apiVersion, requestContextV2, snapshot);\n\n const reqSnap = {\n method: snapshot.method.toUpperCase(),\n headers,\n queryStringParameters,\n pathParameters: matchCtx.pathParameters,\n sourceIp,\n matchedPath: matchCtx.matchedPath,\n stage: matchCtx.route.stage,\n };\n\n const methodArn = buildMethodArn({\n apiId: 'local',\n accountId: '123456789012',\n stage: matchCtx.route.stage,\n method: snapshot.method,\n path: matchCtx.matchedPath,\n });\n\n const cache = opts.authorizerCache;\n\n if (authorizer.kind === 'lambda-token') {\n const token = headers[authorizer.tokenHeader];\n if (!token) {\n return { result: { allow: false }, denyKind: 'missing-identity' };\n }\n if (cache) {\n const cached = cache.get(authorizer.logicalId, hashOne(token));\n if (cached) {\n // Re-evaluate Resource against the current methodArn so a\n // narrow-Resource Allow doesn't leak across routes. Cached\n // entries without a policy (only possible on a deny path,\n // since `parseLambdaAuthorizerResponse` only emits Allow with\n // a populated policy) skip re-eval.\n if (cached.policy !== undefined) {\n return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));\n }\n return shapeOutcome(cached);\n }\n }\n const result = await invokeTokenAuthorizer(authorizer, reqSnap, {\n pool: state.pool,\n rieTimeoutMs: opts.rieTimeoutMs,\n methodArn,\n mockAccountId: '123456789012',\n mockApiId: 'local',\n });\n if (cache && result.identityHash !== undefined) {\n cache.set(\n authorizer.logicalId,\n result.identityHash,\n authorizer.resultTtlSeconds,\n stripHash(result)\n );\n }\n return shapeOutcome(stripHash(result));\n }\n\n if (authorizer.kind === 'lambda-request') {\n // Pre-compute identity hash for cache lookup BEFORE invoking. Pre-fix\n // this code invoked first and consulted the cache only afterwards,\n // defeating the cache for every request.\n const { identityHash, missing } = computeRequestIdentityHash(authorizer, reqSnap);\n if (missing) {\n return { result: { allow: false }, denyKind: 'missing-identity' };\n }\n if (cache && authorizer.resultTtlSeconds > 0) {\n const cached = cache.get(authorizer.logicalId, identityHash);\n if (cached) {\n // For Lambda authorizers we always re-evaluate Resource against\n // the current methodArn (mirrors AWS-deployed API Gateway). The\n // HTTP v2 `{isAuthorized}` simple shape has no policy — those\n // cached entries pass through their own allow flag.\n if (cached.policy !== undefined) {\n return shapeOutcome(evaluateCachedLambdaPolicy(cached, methodArn));\n }\n return shapeOutcome(cached);\n }\n }\n const result = await invokeRequestAuthorizer(authorizer, reqSnap, {\n pool: state.pool,\n rieTimeoutMs: opts.rieTimeoutMs,\n methodArn,\n mockAccountId: '123456789012',\n mockApiId: 'local',\n });\n if (cache && result.identityHash !== undefined && authorizer.resultTtlSeconds > 0) {\n cache.set(\n authorizer.logicalId,\n result.identityHash,\n authorizer.resultTtlSeconds,\n stripHash(result)\n );\n }\n return shapeOutcome(stripHash(result));\n }\n\n if (authorizer.kind === 'iam') {\n // SigV4 signature verification — see `sigv4-verify.ts` for the\n // signing-key reproduction and constant-time compare. No cache\n // (every request carries a unique signature; AWS-deployed API\n // Gateway doesn't cache AWS_IAM auth either). The verifier itself\n // is responsible for the warn-and-pass behavior on foreign-identity\n // requests per `feedback_match_aws_default_over_opinionated.md`.\n if (!opts.sigV4CredentialsLoader) {\n // Defensive: local-start-api always wires this when any IAM\n // route is discovered. Treat absence as policy-deny.\n getLogger().debug(\n `AWS_IAM authorizer for ${matchCtx.route.declaredAt}: no SigV4 credentials loader configured — denying.`\n );\n return { result: { allow: false }, denyKind: 'policy-deny' };\n }\n // OAC-fronted Function URLs always warn-and-pass and ignore\n // `--strict-sigv4`: in production CloudFront re-signs the origin request\n // with its own identity, so the local server never sees a client\n // signature it could verify. `oacFronted` is set only for\n // `AWS::Lambda::Url` routes. See `authorizer-resolver.ts` /\n // `isFunctionUrlOacFronted`.\n const oacFronted = authorizer.oacFronted === true;\n const sigResult = await verifySigV4(\n {\n method: snapshot.method,\n rawUrl: snapshot.rawUrl,\n headers,\n body: snapshot.body,\n },\n opts.sigV4CredentialsLoader,\n {\n ...(opts.sigV4WarnedForeignIds && { warnedForeignIds: opts.sigV4WarnedForeignIds }),\n strict: opts.sigV4Strict === true,\n ...(oacFronted && { oacFronted: true }),\n }\n );\n if (!sigResult.allow) {\n const hasAuth = headers['authorization'] !== undefined;\n return {\n result: { allow: false },\n denyKind: hasAuth ? 'policy-deny' : 'missing-identity',\n };\n }\n return shapeOutcome({\n allow: true,\n ...(sigResult.principalId !== undefined && { principalId: sigResult.principalId }),\n });\n }\n\n if (!opts.jwksCache) {\n // Defensive: should never reach here in practice — local-start-api\n // always passes a JWKS cache when any JWT authorizer is configured.\n return { result: { allow: false }, denyKind: 'policy-deny' };\n }\n\n const authHeader = headers['authorization'];\n const jwksOpts = { ...(opts.jwksWarnedUrls && { warned: opts.jwksWarnedUrls }) };\n if (authorizer.kind === 'cognito') {\n if (cache && authHeader !== undefined) {\n const cached = cache.get(authorizer.logicalId, hashOne(authHeader));\n if (cached) return shapeOutcome(cached);\n }\n const result = await verifyCognitoJwt(authorizer, authHeader, opts.jwksCache, jwksOpts);\n if (cache && result.identityHash !== undefined && result.ttlSeconds > 0) {\n cache.set(\n authorizer.logicalId,\n result.identityHash,\n result.ttlSeconds,\n stripHashAndTtl(result)\n );\n }\n if (!result.allow && authHeader === undefined) {\n return { result: stripHashAndTtl(result), denyKind: 'missing-identity' };\n }\n return shapeOutcome(stripHashAndTtl(result));\n }\n\n // jwt\n if (cache && authHeader !== undefined) {\n const cached = cache.get(authorizer.logicalId, hashOne(authHeader));\n if (cached) return shapeOutcome(cached);\n }\n const result = await verifyJwtAuthorizer(authorizer, authHeader, opts.jwksCache, jwksOpts);\n if (cache && result.identityHash !== undefined && result.ttlSeconds > 0) {\n cache.set(\n authorizer.logicalId,\n result.identityHash,\n result.ttlSeconds,\n stripHashAndTtl(result)\n );\n }\n if (!result.allow && authHeader === undefined) {\n return { result: stripHashAndTtl(result), denyKind: 'missing-identity' };\n }\n return shapeOutcome(stripHashAndTtl(result));\n}\n\n/**\n * Wrap a {@link CachedAuthorizerResult} into the {@link AuthorizerOutcome}\n * shape. Allow → no denyKind; Deny → `'policy-deny'` (the explicit\n * \"authorizer ran and denied\" path; missing-identity is set by the\n * caller before this point).\n */\nfunction shapeOutcome(result: CachedAuthorizerResult): AuthorizerOutcome {\n if (result.allow) return { result };\n return { result, denyKind: 'policy-deny' };\n}\n\n/**\n * Pick the source IP for the authorizer event. REST v1 stores it under\n * `requestContext.identity.sourceIp`; HTTP v2 under `requestContext.http.sourceIp`.\n * Falls back to the snapshot's `sourceIp` (or `127.0.0.1` for the local\n * server) when the structured field is absent.\n */\nfunction pickSourceIp(\n apiVersion: 'v1' | 'v2',\n requestContext: Record<string, unknown>,\n snapshot: HttpRequestSnapshot\n): string {\n if (apiVersion === 'v1') {\n const identity = requestContext['identity'];\n if (\n identity &&\n typeof identity === 'object' &&\n !Array.isArray(identity) &&\n typeof (identity as Record<string, unknown>)['sourceIp'] === 'string'\n ) {\n return (identity as Record<string, unknown>)['sourceIp'] as string;\n }\n } else {\n const http = requestContext['http'];\n if (\n http &&\n typeof http === 'object' &&\n !Array.isArray(http) &&\n typeof (http as Record<string, unknown>)['sourceIp'] === 'string'\n ) {\n return (http as Record<string, unknown>)['sourceIp'] as string;\n }\n }\n return snapshot.sourceIp ?? '127.0.0.1';\n}\n\nfunction buildOverlay(\n authorizer: AuthorizerInfo,\n result: CachedAuthorizerResult,\n routeApiVersion: 'v1' | 'v2'\n): AuthorizerEventOverlay | undefined {\n // PR #515 item 9: `buildAuthorizerContextForServiceIntegration` was\n // extracted into the shared `authorizer-context.ts:buildAuthorizerContextShape`\n // helper. This function (`buildOverlay`) still uses hand-rolled\n // per-kind branching because it wraps the per-kind context in the\n // `AuthorizerEventOverlay` discriminated union shape that\n // `applyAuthorizerOverlay` expects (with the `lambda-http-v2` arm\n // additionally namespaced under `.lambda` at the consumer layer).\n // The inner per-kind shape matches the helper's output exactly, so a\n // future kind addition can be lifted through the shared helper at\n // both call sites with no behavior change.\n if (authorizer.kind === 'lambda-token' || authorizer.kind === 'lambda-request') {\n const isV2 = authorizer.kind === 'lambda-request' && authorizer.apiVersion === 'v2';\n return isV2\n ? {\n kind: 'lambda-http-v2',\n ...(result.principalId !== undefined && { principalId: result.principalId }),\n ...(result.context && { context: result.context }),\n }\n : {\n kind: 'lambda-rest-v1',\n ...(result.principalId !== undefined && { principalId: result.principalId }),\n ...(result.context && { context: result.context }),\n };\n }\n if (authorizer.kind === 'cognito') {\n return { kind: 'cognito-rest-v1', claims: result.context ?? {} };\n }\n if (authorizer.kind === 'iam') {\n // AWS_IAM authorization runs on REST v1 (`AuthorizationType: 'AWS_IAM'`)\n // and on Function URLs (`AuthType: 'AWS_IAM'`, issue #621).\n //\n // REST v1: surface the access-key-id under `authorizer.principalId`\n // via the flat v1 overlay shape. PR #447 established this precedent.\n //\n // Function URL (v2 + iam): emit NO overlay. AWS-deployed Function URLs\n // write principal context under `event.requestContext.authorizer.iam.\n // {accessKey, accountId, callerId, userArn, ...}` — NOT `.lambda` —\n // and cdk-local has no local IAM data plane to synthesize that block (no\n // STS GetCallerIdentity per request, no IAM policy emulation). Emitting\n // the v2 `lambda-http-v2` overlay would write `principalId` under\n // `.lambda.principalId`, which is the wrong namespace and worse than\n // no context: handlers defensive-reading `.iam ?? {}` see an empty\n // object either way, and handlers reading `.lambda` see a misleading\n // value that does not exist on deployed Function URLs. PR body honors\n // this with \"no Function URL identity context\" out-of-scope.\n if (routeApiVersion === 'v2') return undefined;\n return {\n kind: 'lambda-rest-v1',\n ...(result.principalId !== undefined && { principalId: result.principalId }),\n };\n }\n // jwt\n return { kind: 'jwt-http-v2', claims: result.context ?? {} };\n}\n\n/**\n * Map the authorizer rejection to an HTTP status code and body.\n * - REST v1 with AWS_IAM (issue #625) → 403 for both deny kinds (the\n * deployed REST v1 SigV4 layer rejects unsigned requests with 403\n * `{\"message\":\"Missing Authentication Token\"}` and signature/policy\n * failures with 403 `{\"message\":\"Forbidden\"}` — lowercase `message`\n * distinguishes the REST v1 shape from the Function URL shape below).\n * - REST v1, missing identity → 401 `{\"message\":\"Unauthorized\"}`\n * (matches deployed behavior; the route reaches the Method but no\n * identity source is present so the authorizer never runs).\n * - REST v1, policy-deny → 403 `{\"message\":\"Forbidden\"}` (the\n * authorizer ran and denied; status mirrors AWS API Gateway).\n * - HTTP v2, both kinds → 401 `{\"message\":\"Unauthorized\"}` (HTTP API\n * collapses both into the same response).\n * - Function URL with AWS_IAM (issue #621) → 403 `{\"Message\":\"Forbidden\"}`\n * for both deny kinds (matches Lambda's deployed Function URL IAM\n * behavior — the AWS SigV4 layer rejects with 403, not 401). Note the\n * capital `Message` — distinct from REST v1 AWS_IAM's lowercase\n * `message`.\n */\nexport function writeAuthRejection(\n res: ServerResponse,\n apiVersion: 'v1' | 'v2',\n denyKind: 'missing-identity' | 'policy-deny',\n authorizerKind?: AuthorizerInfo['kind']\n): void {\n // REST v1 with AWS_IAM rides the same SigV4 verifier as the v2 path\n // (PR #447), but the AWS-deployed REST v1 surface rejects with 403 +\n // lowercase `message` — distinct from both v1's default authorizer\n // 401 (the non-IAM path below) and Function URL's capital `Message`.\n if (apiVersion === 'v1' && authorizerKind === 'iam') {\n if (denyKind === 'missing-identity') {\n writeError(res, 403, '{\"message\":\"Missing Authentication Token\"}');\n return;\n }\n writeError(res, 403, '{\"message\":\"Forbidden\"}');\n return;\n }\n // Function URLs are v2-shaped but Lambda's IAM rejection differs from\n // API Gateway v2's default 401 — match the deployed response so a dev\n // exercising AWS_IAM Function URLs sees the same status code locally.\n if (apiVersion === 'v2' && authorizerKind === 'iam') {\n writeError(res, 403, '{\"Message\":\"Forbidden\"}');\n return;\n }\n if (apiVersion === 'v2') {\n writeError(res, 401, '{\"message\":\"Unauthorized\"}');\n return;\n }\n if (denyKind === 'missing-identity') {\n writeError(res, 401, '{\"message\":\"Unauthorized\"}');\n return;\n }\n writeError(res, 403, '{\"message\":\"Forbidden\"}');\n}\n\nfunction hashOne(value: string): string {\n return value;\n}\n\nfunction stripHash(r: CachedAuthorizerResult & { identityHash?: unknown }): CachedAuthorizerResult {\n const { identityHash, ...rest } = r;\n void identityHash;\n return rest;\n}\n\nfunction stripHashAndTtl(\n r: CachedAuthorizerResult & { identityHash?: unknown; ttlSeconds?: unknown }\n): CachedAuthorizerResult {\n const { identityHash, ttlSeconds, ...rest } = r;\n void identityHash;\n void ttlSeconds;\n return rest;\n}\n\n/**\n * Lowercase header names + comma-join multiple values (matches v2 shape).\n */\nfunction lowercaseSingularHeaders(raw: Record<string, string[]>): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, values] of Object.entries(raw)) {\n out[name.toLowerCase()] = values.join(',');\n }\n return out;\n}\n\n/**\n * Parse query string into a singular map. Multi-value keys are\n * comma-joined in order of appearance (`?foo=a&foo=b` -> `foo: 'a,b'`)\n * — matches the contract documented at\n * `src/local/parameter-mapping.ts:14` (\"multi-values comma-joined\")\n * AND deployed API Gateway behavior. Used by both the authorizer pass\n * and the HTTP API v2 service-integration parameter mapper.\n */\nexport function parseQueryStringSingular(rawUrl: string): Record<string, string> {\n const q = rawUrl.indexOf('?');\n if (q < 0) return {};\n const raw = rawUrl.slice(q + 1);\n if (raw.length === 0) return {};\n const out: Record<string, string> = {};\n for (const pair of raw.split('&')) {\n if (pair.length === 0) continue;\n const eq = pair.indexOf('=');\n const rawKey = eq === -1 ? pair : pair.slice(0, eq);\n const rawValue = eq === -1 ? '' : pair.slice(eq + 1);\n let key = rawKey;\n let value = rawValue;\n try {\n key = decodeURIComponent(rawKey);\n } catch {\n /* keep raw */\n }\n try {\n value = decodeURIComponent(rawValue);\n } catch {\n /* keep raw */\n }\n const prev = out[key];\n out[key] = prev === undefined ? value : `${prev},${value}`;\n }\n return out;\n}\n\n/**\n * Drain the request body into a Buffer. Local-only server — eager read\n * is fine; v1 makes no attempt to stream.\n */\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise<Buffer>((resolveBody, rejectBody) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer | string) => {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n });\n req.on('end', () => resolveBody(Buffer.concat(chunks)));\n req.on('error', rejectBody);\n });\n}\n\n/**\n * Collect headers from the IncomingMessage as a name → values[] map (the\n * shape `buildHttpApiV2Event` consumes). Node's `req.headers` already\n * lowercases names, but we keep them as-is and let the event-builder\n * normalize so the same request snapshot can be replayed in tests.\n *\n * `set-cookie` is the only header Node returns as `string[]`; we\n * normalize every other field by wrapping in `[v]` so the downstream\n * code never has to special-case array-vs-string.\n */\nfunction collectHeaders(req: IncomingMessage): Record<string, string[]> {\n const out: Record<string, string[]> = {};\n for (const [name, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) {\n out[name] = value;\n } else if (typeof value === 'string') {\n out[name] = [value];\n }\n }\n return out;\n}\n\n/**\n * Write a small JSON error response. Used when the server cannot reach\n * the handler at all (no matching route, container acquire failed, RIE\n * unreachable).\n */\nfunction writeError(\n res: ServerResponse,\n statusCode: number,\n body = '{\"message\":\"Internal server error\"}'\n): void {\n res.statusCode = statusCode;\n res.setHeader('content-type', 'application/json');\n res.setHeader('content-length', String(Buffer.byteLength(body, 'utf-8')));\n res.end(body);\n}\n\n/**\n * Handle an HTTP API v2 service-integration route (#458). The route\n * carries `serviceIntegration: { subtype, requestParameters, responseParameters? }`\n * — no Lambda backs it. Flow:\n *\n * 1. Build a {@link RequestParameterContext} from the HTTP request +\n * route match (path parameters, query string, headers, context\n * variables, stage variables).\n * 2. Resolve every `RequestParameters` value against that context via\n * `parameter-mapping.ts`. Per-parameter unresolved refs degrade to\n * `\"\"` (matches AWS deployed behavior).\n * 3. Dispatch to the per-subtype SDK adapter in\n * `httpv2-service-integration.ts`. Per-request `Region` parameter\n * overrides the server's `opts.defaultRegion`; absent both surfaces\n * a 400.\n * 4. Apply per-status-code `ResponseParameters` overlay (header /\n * statuscode overwrites).\n * 5. Write the result to the HTTP client.\n *\n * Authorizer pass: NOT yet wired (current scope is dispatch only). When\n * a service-integration route carries an authorizer, the discovery layer\n * leaves it in place but the server short-circuits to dispatch BEFORE the\n * auth pass. A follow-up PR can hoist the auth pass earlier — keeping it\n * out of this PR limits the blast radius.\n */\nasync function handleServiceIntegrationRequest(\n req: IncomingMessage,\n res: ServerResponse,\n match: { route: DiscoveredRoute; pathParameters: Record<string, string> },\n bodyBuf: Buffer,\n opts: StartApiServerOptions,\n authorizer: AuthorizerInfo | undefined,\n authResult: CachedAuthorizerResult | undefined\n): Promise<void> {\n const route = match.route;\n const svc = route.serviceIntegration;\n if (!svc) {\n // Defensive — caller already gated on this branch.\n writeError(res, 500);\n return;\n }\n const rawUrl = req.url ?? '/';\n const headersFlat = flattenHeadersForMapping(req);\n const queryString = parseQueryStringSingular(rawUrl);\n const requestPath = rawUrl.split('?')[0] ?? '/';\n const context = buildServiceIntegrationContextVars(req, route);\n // Surface the authorizer's verdict to the parameter-mapping context so\n // `$context.authorizer.X` selection expressions resolve on auth-\n // protected service-integration routes (#502). The shape mirrors\n // `applyAuthorizerOverlay`'s event-level overlay — Lambda authorizer\n // context fields land flat under `authorizer`, JWT/Cognito claims\n // land under `authorizer.jwt.claims` / `authorizer.claims`.\n const authorizerCtx = buildAuthorizerContextForServiceIntegration(authorizer, authResult);\n const ctx: RequestParameterContext = {\n headers: headersFlat,\n queryString,\n pathParameters: match.pathParameters,\n requestPath,\n body: bodyBuf.toString('utf8'),\n context,\n stageVariables: route.stageVariables ?? {},\n ...(authorizerCtx && { authorizer: authorizerCtx }),\n };\n const outcome = resolveServiceIntegrationParameters(svc.requestParameters, ctx);\n if (outcome.kind === 'error') {\n getLogger().warn(`[${route.declaredAt}] ${outcome.reason}`);\n const body = JSON.stringify({ message: 'Invalid integration mapping', reason: outcome.reason });\n res.statusCode = 500;\n res.setHeader('content-type', 'application/json');\n res.setHeader('content-length', String(Buffer.byteLength(body, 'utf-8')));\n res.end(body);\n return;\n }\n const result = await dispatchServiceIntegration(\n svc.subtype,\n outcome.resolved,\n opts.defaultRegion ?? ''\n );\n const responseCtx: ResponseParameterContext = {\n context,\n stageVariables: route.stageVariables ?? {},\n };\n const finalResult = applyResponseParameters(result, svc.responseParameters, responseCtx);\n res.statusCode = finalResult.statusCode;\n for (const [name, value] of Object.entries(finalResult.headers)) {\n res.setHeader(name, value);\n }\n res.setHeader('content-length', String(Buffer.byteLength(finalResult.body, 'utf-8')));\n res.end(finalResult.body);\n}\n\n/**\n * Flatten Node's `req.headers` to the single-string-per-key shape the\n * parameter-mapping resolver expects (`$request.header.<name>` is\n * documented as comma-joined multi-value). Header NAMES are lowercased\n * because AWS docs document `$request.header.<name>` as case-insensitive.\n */\nfunction flattenHeadersForMapping(req: IncomingMessage): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [name, value] of Object.entries(req.headers)) {\n const lower = name.toLowerCase();\n if (Array.isArray(value)) {\n out[lower] = value.join(', ');\n } else if (typeof value === 'string') {\n out[lower] = value;\n }\n }\n return out;\n}\n\n/**\n * Build the subset of AWS context variables the service-integration\n * parameter-mapping resolver needs to surface (per AWS docs\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-logging-variables.html).\n *\n * We populate the most-commonly-referenced fields with realistic values\n * (`requestId` is fresh per call, `accountId` / `domainName` are mock\n * but stable). Selection expressions against context variables we\n * don't model resolve to `\"\"` — matches the AWS-deployed behavior of\n * absent values.\n */\nfunction buildServiceIntegrationContextVars(\n req: IncomingMessage,\n route: DiscoveredRoute\n): Record<string, string> {\n const requestId = `${getEmbedConfig().binaryName}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n const sourceIp = req.socket.remoteAddress ?? '127.0.0.1';\n return {\n requestId,\n accountId: '123456789012',\n apiId: route.apiLogicalId ?? 'local',\n stage: route.stage,\n 'identity.sourceIp': sourceIp,\n 'identity.userAgent':\n Array.isArray(req.headers['user-agent']) || typeof req.headers['user-agent'] === 'string'\n ? String(req.headers['user-agent'])\n : '',\n domainName: 'localhost',\n httpMethod: req.method ?? 'GET',\n path: (req.url ?? '/').split('?')[0] ?? '/',\n protocol: 'HTTP/1.1',\n requestTime: new Date().toISOString(),\n requestTimeEpoch: String(Date.now()),\n };\n}\n\n/**\n * Build the `authorizer` field for the parameter-mapping context on\n * service-integration routes (#502). Surfaces the authorizer's verdict\n * in the same shape `applyAuthorizerOverlay` writes onto the Lambda\n * event so users can reference `$context.authorizer.X` /\n * `$context.authorizer.jwt.claims.X` / `$context.authorizer.claims.X`\n * in `RequestParameters`.\n *\n * Per-kind shape:\n * - Lambda authorizers (`lambda-token` / `lambda-request` / `iam`):\n * `principalId` + flat `context` fields land at the top level\n * (`$context.authorizer.principalId`, `$context.authorizer.<key>`).\n * - Cognito (REST v1): claims under `$context.authorizer.claims.X`.\n * - JWT (HTTP v2): claims under `$context.authorizer.jwt.claims.X` +\n * `$context.authorizer.jwt.scopes`.\n *\n * Returns `undefined` when no authorizer fired (route had\n * `AuthorizationType: NONE` / no authorizer attached).\n */\nexport function buildAuthorizerContextForServiceIntegration(\n authorizer: AuthorizerInfo | undefined,\n result: CachedAuthorizerResult | undefined\n): Record<string, unknown> | undefined {\n if (!authorizer || !result) return undefined;\n // PR #515 item 9: delegate to the shared per-kind shape builder so\n // a future shape change (or new kind) lands in one place rather than\n // forking between `buildOverlay` and this helper.\n return buildAuthorizerContextShape(authorizer, result);\n}\n\n/**\n * Write the 501 Not Implemented response surfaced for routes the\n * discovery layer flagged as `unsupported`. The integration's reason\n * (e.g. \"MOCK integration is not emulated\", \"WebSocket APIs are not\n * supported\") is echoed in the body so the user gets a precise pointer\n * at first hit instead of a generic 502.\n */\nfunction writeNotImplemented(res: ServerResponse, reason: string): void {\n const body = JSON.stringify({ message: 'Not Implemented', reason });\n res.statusCode = 501;\n res.setHeader('content-type', 'application/json');\n res.setHeader('content-length', String(Buffer.byteLength(body, 'utf-8')));\n res.end(body);\n}\n\n/**\n * Write the canonical CORS preflight response derived from a REST v1\n * MOCK Method's `Integration.IntegrationResponses[0].ResponseParameters`.\n * Headers are emitted verbatim — the discovery layer already stripped\n * AWS's literal single-quote wrappers and dropped any non-literal\n * (intrinsic-valued) entries.\n */\nfunction writeMockCorsPreflight(\n res: ServerResponse,\n preflight: { statusCode: number; headers: Record<string, string> }\n): void {\n res.statusCode = preflight.statusCode;\n for (const [name, value] of Object.entries(preflight.headers)) {\n res.setHeader(name, value);\n }\n res.end();\n}\n\n/**\n * Extract the verified client certificate from a request's TLS socket.\n *\n * Pre-conditions (load-bearing — caller MUST gate on `opts.mtls`):\n * - The server was started with `https.createServer({requestCert: true,\n * rejectUnauthorized: true, ...})`, so the TLS handshake has\n * already rejected unknown-CA / self-signed / missing-cert clients\n * by the time `handleRequest` runs. Any peer cert we see here is\n * structurally valid against the supplied CA bundle — we do NOT\n * re-verify in code.\n *\n * Returns `undefined` when the request was not over a TLS socket (the\n * caller should NOT call this on plain-HTTP requests; the gate is the\n * `opts.mtls` check in `handleRequest`).\n *\n * The returned shape is the AWS-canonical\n * `requestContext.identity.clientCert` per\n * https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mutual-tls.html#api-gateway-mutual-tls-event-shape:\n *\n * {\n * clientCertPem: \"-----BEGIN CERTIFICATE-----\\n...\",\n * subjectDN: \"CN=client,O=example,C=US\",\n * issuerDN: \"CN=My CA,O=example,C=US\",\n * serialNumber: \"01:23:45:67:...\",\n * validity: { notBefore: \"May 22 03:30:00 2026 GMT\",\n * notAfter: \"May 22 03:30:00 2027 GMT\" }\n * }\n *\n * Exported for unit testing — the helper is pure-functional given a\n * cert object and never touches the network.\n */\nexport function extractClientCert(req: IncomingMessage): Record<string, unknown> | undefined {\n const socket = req.socket as TLSSocket;\n // Plain-HTTP socket guard: `getPeerCertificate` is the discriminator\n // for TLSSocket vs net.Socket. We test for the method's presence\n // rather than `socket instanceof TLSSocket` because the latter\n // requires importing the runtime class (overkill for a type guard).\n if (typeof socket.getPeerCertificate !== 'function') return undefined;\n const cert = socket.getPeerCertificate(false);\n return peerCertificateToAws(cert);\n}\n\n/**\n * Convert Node's `PeerCertificate` object to the AWS-canonical\n * `clientCert` event shape. Exported separately from\n * {@link extractClientCert} so the conversion can be unit-tested\n * against a synthetic cert object without a real TLS socket.\n *\n * Returns `undefined` when the cert is empty (`getPeerCertificate`\n * returns `{}` when there is no peer cert). Otherwise emits every\n * field defined by the AWS shape, falling back to `''` for missing\n * subject / issuer DN segments so handlers do not need to null-check.\n */\nexport function peerCertificateToAws(\n cert: PeerCertificate | DetailedPeerCertificate | Record<string, unknown> | undefined | null\n): Record<string, unknown> | undefined {\n if (!cert || typeof cert !== 'object') return undefined;\n // Node returns `{}` for an empty / missing cert; treat that as\n // \"no cert\" rather than emitting a placeholder. The TLS handshake\n // gate (rejectUnauthorized: true) should make this case unreachable\n // when mTLS is configured correctly, but the guard keeps us safe\n // against a misconfigured trust-store + cert combo.\n if (Object.keys(cert).length === 0) return undefined;\n\n const c = cert as Record<string, unknown>;\n const subject = c['subject'];\n const issuer = c['issuer'];\n const raw = c['raw'];\n const subjectDN = formatDN(subject);\n const issuerDN = formatDN(issuer);\n const serialNumber = typeof c['serialNumber'] === 'string' ? (c['serialNumber'] as string) : '';\n const validity = {\n notBefore: typeof c['valid_from'] === 'string' ? (c['valid_from'] as string) : '',\n notAfter: typeof c['valid_to'] === 'string' ? (c['valid_to'] as string) : '',\n };\n // Node's PeerCertificate.raw is a Buffer holding the DER-encoded\n // certificate. AWS exposes the PEM-encoded form; we emit PEM when we\n // have the raw bytes (the common case) and fall back to an empty\n // string when only the parsed metadata is available.\n const clientCertPem = Buffer.isBuffer(raw) ? derBufferToPem(raw) : '';\n return {\n clientCertPem,\n subjectDN,\n issuerDN,\n serialNumber,\n validity,\n };\n}\n\n/**\n * Format a Node `subject` / `issuer` object (e.g.\n * `{C: 'US', O: 'example', CN: 'client'}`) as the canonical\n * comma-separated DN string AWS emits (`CN=client,O=example,C=US`).\n *\n * Ordering follows AWS / OpenSSL convention: CN first, then OU, O, L,\n * ST, C. Fields the cert does not declare are skipped silently.\n */\nfunction formatDN(dn: unknown): string {\n if (!dn || typeof dn !== 'object') return '';\n const obj = dn as Record<string, unknown>;\n const order = ['CN', 'OU', 'O', 'L', 'ST', 'C'];\n const parts: string[] = [];\n for (const key of order) {\n const v = obj[key];\n if (typeof v === 'string' && v.length > 0) {\n parts.push(`${key}=${v}`);\n }\n }\n return parts.join(',');\n}\n\n/**\n * Encode a DER-encoded certificate Buffer as PEM. We wrap the base64\n * in 64-char-per-line segments the way `openssl x509` does so the\n * round-trip looks like what AWS API Gateway emits.\n */\nfunction derBufferToPem(der: Buffer): string {\n const b64 = der.toString('base64');\n const lines: string[] = [];\n for (let i = 0; i < b64.length; i += 64) {\n lines.push(b64.slice(i, i + 64));\n }\n return `-----BEGIN CERTIFICATE-----\\n${lines.join('\\n')}\\n-----END CERTIFICATE-----\\n`;\n}\n\n/**\n * Read mTLS materials from disk. Each path is a PEM file. The function\n * throws a wrapped error naming the offending path on `ENOENT` /\n * permission failures so the CLI surfaces a clear error before the\n * server starts.\n *\n * Exported for the CLI's resolve-then-construct flow + for unit tests.\n */\nexport function readMtlsMaterialsFromDisk(opts: {\n truststorePath: string;\n certPath: string;\n keyPath: string;\n}): MtlsServerConfig {\n return {\n caPem: readPemOrThrow(opts.truststorePath, '--mtls-truststore'),\n certPem: readPemOrThrow(opts.certPath, '--mtls-cert'),\n keyPem: readPemOrThrow(opts.keyPath, '--mtls-key'),\n };\n}\n\nfunction readPemOrThrow(path: string, flagName: string): Buffer {\n try {\n return readFileSync(path);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`${flagName}: cannot read PEM file at '${path}': ${msg}`);\n }\n}\n\n// Keep DiscoveredRoute import alive for downstream consumers reading\n// from this module's namespace export.\nexport type { DiscoveredRoute };\n","import type { RouteWithAuth } from './authorizer-resolver.js';\n\n/**\n * One group of routes that share a single API surface — and therefore\n * a single local HTTP server in `cdkl start-api` (issue #260).\n *\n * Pre-PR `cdkl start-api` lumped every discovered API into one\n * HTTP server on one port. That broke realistic CDK apps with multiple\n * APIs (e.g. an admin API with Cognito auth and a public API with no\n * auth): authorizers, CORS configs, and stage variables are all\n * per-API, and lumping them into one server forced an awkward \"first\n * match wins\" semantic that did not mirror AWS Lambda's actual\n * routing.\n *\n * Post-PR the CLI launches **one server per group** so each API gets\n * its own port, its own authorizer pipeline, its own CORS config, and\n * its own container pool. The grouping rule:\n *\n * - `AWS::ApiGateway::RestApi` → one group per RestApi logical id\n * - `AWS::ApiGatewayV2::Api` → one group per HTTP API logical id\n * - `AWS::Lambda::Url` → one group per Function URL (keyed\n * by the Lambda's logical id, since\n * Function URLs are 1:1 with their\n * backing Lambda and don't share a\n * parent \"Api\" resource)\n *\n * `serverKey` is the stable matching key (used by the reload orchestrator\n * to swap state per server across reloads). `displayName` is what we\n * print in the startup banner / route table — human-readable, includes\n * the API kind in parens for disambiguation.\n */\nexport interface ApiServerGroup {\n /**\n * Stable identity for cross-reload state matching. Format:\n * - `http-api:<stackName>:<apiLogicalId>` (when route has `apiStackName`)\n * - `rest-v1:<stackName>:<apiLogicalId>`\n * - `function-url:<stackName>:<lambdaLogicalId>`\n *\n * Routes without `apiStackName` (templates lacking `aws:cdk:path`\n * metadata, hand-rolled `cfn.Resource` defs, or fixtures from\n * pre-`aws:cdk:path` test code) fall back to the un-prefixed shape:\n * - `http-api:<apiLogicalId>` / `rest-v1:<apiLogicalId>` /\n * `function-url:<lambdaLogicalId>`\n *\n * The stack prefix means cross-stack same-logical-id APIs (a CDK app\n * with `MyHttpApi` in both `WebStack` and `AdminStack`) get **two\n * separate servers** rather than silently colliding on one serverKey\n * — defense-in-depth on top of the upstream multi-stack bare-id\n * rejection in the CLI command body.\n */\n readonly serverKey: string;\n /** Human-readable name surfaced in logs (e.g. \"MyHttpApi (HTTP API v2)\"). */\n readonly displayName: string;\n /** Discriminator on the kind of API. */\n readonly kind: 'rest-v1' | 'http-api' | 'function-url' | 'websocket';\n /**\n * Logical ID of the parent API resource (or, for Function URLs, the\n * backing Lambda). Useful for `--api <id>` filtering, CORS lookup,\n * and route-grouping diagnostics.\n */\n readonly identifier: string;\n /** Routes that belong to this server. Non-empty by construction. */\n readonly routes: readonly RouteWithAuth[];\n}\n\n/**\n * Group a flat list of discovered routes (with authorizer info already\n * attached by `attachAuthorizers`) into one group per local HTTP server.\n *\n * The output order is stable across calls: groups appear in the order\n * their first route appears in the input, which mirrors the user's\n * CDK template traversal order — so the startup banner lists APIs in a\n * predictable order across reloads.\n *\n * Returns an empty array iff `routes` is empty. Callers are expected to\n * surface the \"no routes discovered\" error themselves; this helper does\n * not throw.\n */\nexport function groupRoutesByServer(routes: readonly RouteWithAuth[]): ApiServerGroup[] {\n const order: string[] = [];\n const byKey = new Map<\n string,\n {\n displayName: string;\n kind: ApiServerGroup['kind'];\n identifier: string;\n routes: RouteWithAuth[];\n }\n >();\n\n for (const rwa of routes) {\n const r = rwa.route;\n let serverKey: string;\n let kind: ApiServerGroup['kind'];\n let identifier: string;\n let displayName: string;\n\n // Stack-prefix the serverKey so two stacks with the same bare\n // logical id get **two separate** servers (defense-in-depth — the\n // upstream filter rejects bare ids in multi-stack apps, but\n // unfiltered runs (no `--api` / `<target>`) still need this to\n // disambiguate). Pre-PR the serverKey was just `<kind>:<logicalId>`\n // and cross-stack collisions silently merged into one server.\n // Routes without `apiStackName` (template w/o `aws:cdk:path` /\n // hand-rolled `cfn.Resource`) keep the un-prefixed serverKey\n // shape so the change is non-breaking for those fixtures.\n const stackPrefix = r.apiStackName ? `${r.apiStackName}:` : '';\n\n if (r.source === 'function-url') {\n // Function URLs have no parent API resource — each URL is its own\n // surface, scoped by its backing Lambda's logical id.\n identifier = r.lambdaLogicalId;\n serverKey = `function-url:${stackPrefix}${identifier}`;\n kind = 'function-url';\n displayName = `${identifier} (Function URL)`;\n } else if (r.source === 'http-api') {\n identifier = r.apiLogicalId ?? '<unknown>';\n serverKey = `http-api:${stackPrefix}${identifier}`;\n kind = 'http-api';\n displayName = `${identifier} (HTTP API v2)`;\n } else {\n // rest-v1\n identifier = r.apiLogicalId ?? '<unknown>';\n serverKey = `rest-v1:${stackPrefix}${identifier}`;\n kind = 'rest-v1';\n displayName = `${identifier} (REST API v1)`;\n }\n\n const existing = byKey.get(serverKey);\n if (existing) {\n existing.routes.push(rwa);\n } else {\n byKey.set(serverKey, { displayName, kind, identifier, routes: [rwa] });\n order.push(serverKey);\n }\n }\n\n return order.map((key) => {\n const entry = byKey.get(key)!;\n return {\n serverKey: key,\n displayName: entry.displayName,\n kind: entry.kind,\n identifier: entry.identifier,\n routes: entry.routes,\n };\n });\n}\n\n/**\n * Filter the route list to a single API by user-supplied identifier.\n *\n * Accepts four input forms — matches the rest of the `cdkl *`\n * target-resolution family (`local invoke <target>` /\n * `local run-task <target>`) for consistency:\n *\n * 1. **Bare logical id** (`MyHttpApi`) — exact match on the parent\n * API's logical id, or on the backing Lambda's logical id for\n * Function URLs.\n * 2. **Stack-qualified logical id** (`MyStack:MyHttpApi`) — exact\n * match on `<stackName>:<logicalId>`. Useful in multi-stack apps\n * where the same bare logical id appears in two stacks.\n * 3. **CDK Construct path / display path** (`MyStack/MyHttpApi`) —\n * exact match on the resource's `aws:cdk:path` metadata.\n * 4. **CDK Construct path prefix** — when the input is a strict\n * ancestor of the resource's `aws:cdk:path` (i.e.\n * `cdkPath.startsWith(input + '/')`). Mirrors the upstream CDK\n * construct-path prefix rule so an L2 wrapper path resolves to its\n * L1 child (`MyStack/MyHttpApi` matches `MyStack/MyHttpApi/Resource`).\n *\n * Routes discovered before this field set was added (or routes where\n * the synthesized template doesn't carry `aws:cdk:path` metadata —\n * e.g. hand-rolled `cfn.Resource` defs) silently fall through to the\n * bare-logical-id-only path so the change is non-breaking.\n *\n * Returns an empty array when no route matches — the caller is\n * responsible for surfacing a \"no API matched\" error with the list of\n * available identifiers (see {@link availableApiIdentifiers}).\n */\nexport function filterRoutesByApiIdentifier(\n routes: readonly RouteWithAuth[],\n identifier: string\n): RouteWithAuth[] {\n return routes.filter((rwa) => routeMatchesIdentifier(rwa.route, identifier));\n}\n\n/**\n * Filter the route list to the UNION of several user-supplied\n * identifiers — the variadic `cdkl start-api <target...>` shape, where\n * passing two or more API identifiers serves exactly that subset (each\n * on its own port, via {@link groupRoutesByServer}).\n *\n * A route is kept when it matches ANY of the identifiers (same matching\n * rules as {@link filterRoutesByApiIdentifier}). Output order is the\n * input route order, so {@link groupRoutesByServer}'s stable grouping is\n * preserved. An empty `identifiers` list returns every route unchanged\n * (the \"serve all\" default path never calls this with an empty set, but\n * the no-op behavior keeps the helper total).\n *\n * Returns an empty array when no route matches any identifier — the\n * caller surfaces a \"no API matched\" error with the available\n * identifiers (see {@link availableApiIdentifiers}).\n */\nexport function filterRoutesByApiIdentifiers(\n routes: readonly RouteWithAuth[],\n identifiers: readonly string[]\n): RouteWithAuth[] {\n if (identifiers.length === 0) return [...routes];\n return routes.filter((rwa) => identifiers.some((id) => routeMatchesIdentifier(rwa.route, id)));\n}\n\n/**\n * Predicate behind {@link filterRoutesByApiIdentifier} and\n * {@link availableApiIdentifiers}'s primary-form selection. Exported\n * for test coverage only — the production code path goes through\n * `filterRoutesByApiIdentifier`.\n */\nexport function routeMatchesIdentifier(route: RouteWithAuth['route'], identifier: string): boolean {\n const bareId = route.source === 'function-url' ? route.lambdaLogicalId : route.apiLogicalId;\n if (bareId && bareId === identifier) return true;\n if (route.apiStackName) {\n if (bareId && identifier === `${route.apiStackName}:${bareId}`) return true;\n }\n if (route.apiCdkPath) {\n if (identifier === route.apiCdkPath) return true;\n if (route.apiCdkPath.startsWith(`${identifier}/`)) return true;\n }\n return false;\n}\n\n/**\n * Enumerate every distinct API identifier in the route list, in the\n * order they were discovered. Useful for the \"available APIs\" error\n * message when `--api <id>` doesn't match.\n *\n * Returns the **primary form** per API (CDK Construct path when\n * available, else bare logical id) — the \"available identifiers\" hint\n * stays compact while pointing users at the form most likely to round-\n * trip across rename refactors.\n */\nexport function availableApiIdentifiers(routes: readonly RouteWithAuth[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const rwa of routes) {\n const r = rwa.route;\n const bareId =\n r.source === 'function-url' ? r.lambdaLogicalId : (r.apiLogicalId ?? '<unknown>');\n const primary = r.apiCdkPath ?? bareId;\n if (!seen.has(primary)) {\n seen.add(primary);\n out.push(primary);\n }\n }\n return out;\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { pickRefLogicalId } from './intrinsic-utils.js';\nimport type { DiscoveredRoute } from './route-discovery.js';\n\n/**\n * Per-API stage selection and stage-variable lookup for `cdk-local\n * start-api` (PR 8c, issue #235).\n *\n * Background: PR 8a hardcoded `event.stageVariables = null` for every\n * route (cited in `local-start-api.ts`'s out-of-scope list). That blocked\n * any local handler that reads its API Gateway Stage Variables —\n * `event.stageVariables.foo` returned `null.foo` and crashed.\n *\n * This module walks the synthesized template once at server boot (and\n * again on every hot-reload) and produces a `Map<apiLogicalId,\n * ResolvedStage>` keyed by the **API resource's** logical id (the\n * `AWS::ApiGateway::RestApi` or `AWS::ApiGatewayV2::Api`). Routes\n * resolve their `stageVariables` by looking up their API's logical id\n * in that map — see `attachStageContext` below.\n *\n * Stage selection rules (locked in the issue brief):\n * - Default: the **first** Stage attached to the RestApi/Api in the\n * order they appear in `template.Resources`. This matches what PR 8a\n * did for `requestContext.stage`.\n * - `--stage <name>` override: select the Stage whose `StageName`\n * property equals the given name. When the user passes `--stage` and\n * no API has a Stage with that name, that's a hard CLI error — but\n * this module only surfaces the resolution result; the CLI raises.\n * - `Function URL` routes don't have a Stage (and the CDK construct\n * never emits one); their `stageVariables` stays `null` and their\n * `stage` stays `'$default'` (PR 8a's behavior).\n */\n\n/**\n * Resolved per-API stage info. `apiVersion` discriminates the two\n * AWS Stage resource types — REST v1 uses `Variables`, HTTP API v2 uses\n * `StageVariables`. We normalize both to a single `variables` map.\n */\nexport interface ResolvedStage {\n /** Stage logical id in the template (for diagnostics). */\n stageLogicalId: string;\n /** The selected stage's name (from `Properties.StageName`). */\n stageName: string;\n /** Either v1 or v2 — which AWS Stage resource type this came from. */\n apiVersion: 'v1' | 'v2';\n /** Resolved variables map. `null` when the Stage carries no variables. */\n variables: Record<string, string> | null;\n}\n\n/**\n * Build the `apiLogicalId → ResolvedStage` map for a single template.\n *\n * Result-map population rules:\n *\n * - **No matching Stage in template** (the API has zero `AWS::ApiGateway::Stage`\n * / `AWS::ApiGatewayV2::Stage` resources pointing at it): the API\n * never enters the result map. `attachStageContext` then leaves\n * `stageVariables: null` AND keeps `route.stage` at the discovery-\n * time placeholder (`'$default'` for HTTP API v2 / Function URL,\n * or whatever the discovery layer parsed for REST v1).\n *\n * - **`stageOverride` provided but no Stage matches** (e.g. user\n * passed `--stage staging`, API only has `prod` / `dev`): same as\n * above — API is LEFT OUT of the result map. The CLI surfaces a\n * deduplicated warn line for each such API up front so users\n * aren't surprised by silent `stageVariables: null` at runtime.\n *\n * - **Match found**: API enters the result map with the picked\n * Stage's `StageName` + variables. `attachStageContext` then sets\n * `route.stageVariables` from the resolved Stage AND overrides\n * `route.stage` with the picked Stage's `StageName` for **both**\n * REST v1 and HTTP API v2 routes. HTTP API v2's auto-deploy default\n * is `'$default'`, but AWS also supports named stages (the\n * `CreateStage` API accepts any name); when the template carries a\n * named v2 Stage we surface that name through `requestContext.stage`\n * so a handler that reads `event.requestContext.stage` sees the\n * same value AWS would surface in the deployed environment.\n */\nexport function buildStageMap(\n template: CloudFormationTemplate,\n stageOverride?: string\n): Map<string, ResolvedStage> {\n const out = new Map<string, ResolvedStage>();\n const resources = template.Resources ?? {};\n\n // Group every Stage by the API it points at, in template order.\n // `Object.entries` preserves insertion order — CDK emits Stages after\n // their parent API, but we don't actually rely on that ordering; we\n // just pick the first one we find.\n const restStagesByApi = new Map<string, Array<{ id: string; resource: TemplateResource }>>();\n const v2StagesByApi = new Map<string, Array<{ id: string; resource: TemplateResource }>>();\n\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type === 'AWS::ApiGateway::Stage') {\n const apiId = pickRefLogicalId((resource.Properties ?? {})['RestApiId']);\n if (apiId) appendByApi(restStagesByApi, apiId, logicalId, resource);\n } else if (resource.Type === 'AWS::ApiGatewayV2::Stage') {\n const apiId = pickRefLogicalId((resource.Properties ?? {})['ApiId']);\n if (apiId) appendByApi(v2StagesByApi, apiId, logicalId, resource);\n }\n }\n\n for (const [apiId, stages] of restStagesByApi) {\n const picked = pickStage(stages, stageOverride);\n if (picked) out.set(apiId, toResolvedStage(picked, 'v1'));\n }\n for (const [apiId, stages] of v2StagesByApi) {\n const picked = pickStage(stages, stageOverride);\n if (picked) out.set(apiId, toResolvedStage(picked, 'v2'));\n }\n\n return out;\n}\n\n/**\n * Append a Stage record to its API's bucket. Tiny helper so the loop\n * above stays readable.\n */\nfunction appendByApi(\n bucket: Map<string, Array<{ id: string; resource: TemplateResource }>>,\n apiId: string,\n stageId: string,\n resource: TemplateResource\n): void {\n const list = bucket.get(apiId) ?? [];\n list.push({ id: stageId, resource });\n bucket.set(apiId, list);\n}\n\n/**\n * Apply the stage-override / first-match rules to a list of Stage\n * resources for a single API. Returns the picked Stage or `undefined`\n * when no match. The selection is intentionally scoped per-API so a\n * `--stage prod` override against a multi-API app picks the matching\n * Stage on each API (rather than the first Stage globally).\n */\nfunction pickStage(\n stages: Array<{ id: string; resource: TemplateResource }>,\n stageOverride: string | undefined\n): { id: string; resource: TemplateResource } | undefined {\n if (stages.length === 0) return undefined;\n if (stageOverride) {\n for (const s of stages) {\n const props = s.resource.Properties ?? {};\n if (props['StageName'] === stageOverride) return s;\n }\n return undefined;\n }\n return stages[0];\n}\n\n/**\n * Build a `ResolvedStage` from a picked Stage resource. Both REST v1\n * (`Variables`) and HTTP API v2 (`StageVariables`) keys are accepted;\n * non-string values are dropped (CDK templates emit only strings, but\n * defense-in-depth keeps a malformed template from crashing the server).\n */\nfunction toResolvedStage(\n stage: { id: string; resource: TemplateResource },\n apiVersion: 'v1' | 'v2'\n): ResolvedStage {\n const props = stage.resource.Properties ?? {};\n const stageName = typeof props['StageName'] === 'string' ? props['StageName'] : '$default';\n const rawVars = apiVersion === 'v1' ? props['Variables'] : props['StageVariables'];\n let variables: Record<string, string> | null = null;\n if (rawVars && typeof rawVars === 'object' && !Array.isArray(rawVars)) {\n const map: Record<string, string> = {};\n for (const [k, v] of Object.entries(rawVars as Record<string, unknown>)) {\n if (typeof v === 'string') {\n map[k] = v;\n }\n // Intrinsic-valued entries (Ref / Fn::GetAtt / etc.) are dropped —\n // mirrors PR 1's env-var policy. The local server has no deploy\n // state to substitute them against; surfacing them as the literal\n // CFn intrinsic object would crash any handler that JSON-stringifies\n // `event.stageVariables`.\n }\n variables = Object.keys(map).length > 0 ? map : null;\n }\n return { stageLogicalId: stage.id, stageName, apiVersion, variables };\n}\n\n/**\n * Mutate every route in `routes` to set `stageVariables` and (for routes\n * that map to a resolved Stage) override `stage` with the resolved\n * Stage's `StageName`. Function URL routes are left untouched.\n *\n * Invariants:\n *\n * - Routes whose `apiLogicalId` is missing from `stageMap` get\n * `stageVariables: null`. For REST v1 that means \"no Stage attached\"\n * (already represented by the discovery layer's `'$default'` placeholder).\n * For HTTP API that means the user passed `--stage <name>` and no\n * Stage on this API matched — the CLI is expected to surface a warn\n * line up front; we just leave the route's variables null here.\n *\n * - Routes whose `apiLogicalId` IS in the map get `stageVariables` set\n * to the resolved Stage's variables (`null` when the Stage has none),\n * and `route.stage` is overridden with the Stage's `StageName` for\n * **both** REST v1 and HTTP API v2 routes. HTTP API v2's default\n * auto-deployed stage is `$default`, but AWS supports named stages\n * on v2 too (`CreateStage` accepts any name) — when the template\n * carries one, surface it through `requestContext.stage` so the\n * local event matches what AWS would emit at the deployed endpoint.\n */\nexport function attachStageContext(\n routes: DiscoveredRoute[],\n stageMap: Map<string, ResolvedStage>\n): void {\n for (const route of routes) {\n if (!route.apiLogicalId) {\n route.stageVariables = null;\n continue;\n }\n const stage = stageMap.get(route.apiLogicalId);\n if (!stage) {\n route.stageVariables = null;\n continue;\n }\n route.stageVariables = stage.variables;\n route.stage = stage.stageName;\n }\n}\n","import * as chokidar from 'chokidar';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Debounced file watcher used by `cdkl start-api --watch`\n * (PR 8c, issue #235).\n *\n * Wraps {@link chokidar.watch} with a 500ms debounce window so a burst\n * of file writes (e.g. an editor save plus its sidecar files, or a\n * `tsc` emit) collapses to one `'reload'` event.\n *\n * `--watch` watches the CDK app's source tree (the directory holding\n * `cdk.json`), so editing handler / construct source re-synths and\n * hot-reloads. The synth output directory (`cdk.out/`) is excluded via\n * {@link FileWatcherOptions.ignored} so the reload's own re-synth writes\n * never re-trigger the watcher (no self-fire loop).\n *\n * Emits a single `'reload'` callback per debounce window. Does NOT\n * pass the changed file path — the orchestrator re-runs the full\n * synth -> discover -> diff sequence regardless of which file changed,\n * because `cdk synth` rewrites template + asset paths atomically and\n * the orchestrator can't reason about partial updates.\n */\n\nexport interface FileWatcher {\n /** Stop watching everything. */\n close(): Promise<void>;\n}\n\nexport interface FileWatcherOptions {\n /** Initial set of paths to watch. */\n paths: readonly string[];\n /** Callback fired (debounced) when any watched path changes. */\n onChange: () => void;\n /** Debounce window in ms. Default 500ms (issue brief). */\n debounceMs?: number;\n /**\n * Pass `true` to suppress the initial `'add'` event chokidar\n * normally fires for every existing file when the watcher starts up\n * — without this, watching the app source tree would fire `'reload'`\n * immediately for every existing source file. Default `true`.\n */\n ignoreInitial?: boolean;\n /**\n * Predicate forwarded to chokidar's `ignored` option. Returning\n * `true` prunes the path (and, for a directory, its whole subtree)\n * from the watch. Used to exclude the synth output directory,\n * `node_modules`, `.git`, and `cdk.json` `watch.exclude` globs when\n * watching the app source tree. Decides on the path alone so it is\n * safe on chokidar's pre-stat probe call (which omits `stats`).\n */\n ignored?: (path: string) => boolean;\n /**\n * Per-path gate checked on each raw chokidar event BEFORE the\n * debounce timer is (re)armed. Returning `false` drops the event so\n * it does not trigger a reload — used to honor `cdk.json`\n * `watch.include` (only matching source files trigger a reload).\n * Unlike {@link FileWatcherOptions.ignored} this never affects\n * chokidar's directory traversal, so include filtering can't prune a\n * directory the watcher must descend into. Default: every event\n * fires.\n */\n shouldTrigger?: (path: string) => boolean;\n}\n\nconst DEFAULT_DEBOUNCE_MS = 500;\n\n/**\n * Construct a {@link FileWatcher}. The watcher is active immediately\n * (chokidar starts listening before this function returns); the\n * caller does not need to `await` ready.\n *\n * Errors from chokidar (typically \"ENOENT: path doesn't exist\") are\n * logged at debug and otherwise swallowed — the start-api server\n * should keep serving even when one of the watched asset directories\n * goes missing during a reload.\n */\nexport function createFileWatcher(options: FileWatcherOptions): FileWatcher {\n const logger = getLogger().child('start-api-watch');\n const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n const ignoreInitial = options.ignoreInitial !== false;\n\n const watcher = chokidar.watch([...options.paths], {\n ignoreInitial,\n // Don't follow symlinks — asset directories under `cdk.out/asset.<hash>/`\n // are real directories; following symlinks would risk cycling into\n // `node_modules` or similar.\n followSymlinks: false,\n // Don't crash on permission errors.\n ignorePermissionErrors: true,\n ...(options.ignored && { ignored: options.ignored }),\n });\n\n let timer: NodeJS.Timeout | null = null;\n // `closed` is checked in BOTH `fire()` (so a chokidar event arriving\n // mid-`close()` doesn't schedule a fresh debounce timer) AND inside\n // the timer callback (so a timer already armed before `close()` was\n // called doesn't invoke `onChange` after the watcher is gone). The\n // belt-and-braces guard closes the race the PR review caught: pre-fix,\n // `close()` cleared `timer` and awaited `watcher.close()`, but a\n // chokidar event arriving DURING that await would arm a fresh timer\n // whose callback then ran `onChange()` against a now-closed server.\n let closed = false;\n const fire = (): void => {\n if (closed) return;\n if (timer) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = null;\n if (closed) return;\n try {\n options.onChange();\n } catch (err) {\n logger.warn(`onChange callback threw: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, debounceMs);\n timer.unref?.();\n };\n\n // Apply the optional include gate before arming the debounce. A\n // dropped event leaves any already-armed timer in place — a prior\n // qualifying event still reloads.\n const onEvent = (path: string): void => {\n if (options.shouldTrigger && !options.shouldTrigger(path)) return;\n fire();\n };\n\n // Subscribe to every file-changing event chokidar emits. We\n // intentionally don't subscribe to `'addDir'` / `'unlinkDir'` because\n // those fire for every nested directory chokidar discovers at\n // start-up; the surrounding `'add'` / `'unlink'` events are enough\n // for our purposes (a directory rename that doesn't change any file\n // contents shouldn't trigger a hot reload).\n watcher.on('add', onEvent);\n watcher.on('change', onEvent);\n watcher.on('unlink', onEvent);\n\n watcher.on('error', (err) => {\n logger.debug(\n `chokidar error: ${err instanceof Error ? err.message : String(err)}. Continuing.`\n );\n });\n\n return {\n close: async (): Promise<void> => {\n closed = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n await watcher.close();\n },\n };\n}\n","/**\n * TTL-aware authorizer-result cache for `cdkl start-api` (PR 8b).\n *\n * AWS API Gateway caches authorizer results per `(authorizer, identity)`\n * tuple to avoid invoking the Lambda / re-verifying the JWT on every\n * request. cdk-local mirrors that behavior locally so the dev experience\n * (latency, log noise) matches deployed behavior.\n *\n * Implementation:\n * - Keyed by `<authorizerLogicalId>\\u0000<identityHash>` (control char\n * separator — cannot collide with valid header / token values).\n * - Per-entry expiry is enforced lazily on `get()`; entries past their\n * deadline are deleted and treated as cache misses.\n * - `set(key, ttlSeconds, result)` with `ttlSeconds === 0` is a no-op\n * (the AWS-side default for HTTP v2 JWT authorizers — no caching).\n *\n * The cache is **per-server-instance** — a shutdown discards every entry,\n * matching the deployed behavior where each API Gateway stage has its\n * own cache scope.\n */\n\nexport interface CachedAuthorizerResult {\n /**\n * Whether the authorizer Allow'd the request.\n *\n * **Cache semantics caveat**: for Lambda authorizers the cache stores\n * the authorizer's verdict shape (`principalId`, `policyDocument`,\n * `context`) keyed by `(authorizerLogicalId, identityHash)`; each\n * cache hit re-evaluates `policyDocument.Resource` against the\n * current request's methodArn so a narrow-Resource policy doesn't\n * leak across routes. So for Lambda authorizers the cached `allow`\n * is the verdict at cache-write time and is recomputed by the\n * caller; for JWT / Cognito (where there is no IAM policy and only\n * Allow results are ever cached) it is the final verdict.\n */\n allow: boolean;\n /**\n * The principal id from the policy document (or the JWT `sub` claim).\n * Surfaced in the request context for the route handler to log.\n */\n principalId?: string;\n /**\n * The authorizer's `context` (Lambda) or claims (JWT) — propagated\n * into `event.requestContext.authorizer` per\n * api-gateway-event.ts.\n */\n context?: Record<string, unknown>;\n /**\n * Original Lambda authorizer policy document (REST v1 + HTTP v2 IAM\n * shape) — surfaced verbatim into\n * `event.requestContext.authorizer.policy` for parity with the\n * deployed behavior, AND used to re-evaluate `Resource` against the\n * current request's methodArn on every cache hit so a narrow-Resource\n * policy can't leak across routes. JWT authorizers omit this.\n */\n policy?: unknown;\n}\n\ninterface Entry {\n expiresAt: number;\n result: CachedAuthorizerResult;\n}\n\nexport interface AuthorizerCache {\n /**\n * Look up `(authorizerLogicalId, identityHash)`. Returns the cached\n * result when present and not yet expired; expired entries are evicted\n * lazily. Returns `undefined` on cache miss.\n */\n get(authorizerLogicalId: string, identityHash: string): CachedAuthorizerResult | undefined;\n /**\n * Cache the result for `ttlSeconds`. A TTL of 0 is a documented no-op.\n */\n set(\n authorizerLogicalId: string,\n identityHash: string,\n ttlSeconds: number,\n result: CachedAuthorizerResult\n ): void;\n /** Clear every entry. Mostly useful for tests. */\n clear(): void;\n /** Diagnostic: number of currently-cached (non-expired) entries. */\n size(): number;\n}\n\n/**\n * Construct a fresh authorizer cache. `now` is injectable for tests so\n * we can advance time without `vi.useFakeTimers()` ceremony at every\n * call site.\n */\nexport function createAuthorizerCache(opts: { now?: () => number } = {}): AuthorizerCache {\n const now = opts.now ?? ((): number => Date.now());\n const map = new Map<string, Entry>();\n\n const buildKey = (auth: string, identity: string): string => `${auth}\\u0000${identity}`;\n\n const sweep = (): void => {\n const t = now();\n for (const [k, v] of map) {\n if (v.expiresAt <= t) map.delete(k);\n }\n };\n\n return {\n get(authorizerLogicalId, identityHash) {\n const key = buildKey(authorizerLogicalId, identityHash);\n const entry = map.get(key);\n if (!entry) return undefined;\n if (entry.expiresAt <= now()) {\n map.delete(key);\n return undefined;\n }\n return entry.result;\n },\n\n set(authorizerLogicalId, identityHash, ttlSeconds, result) {\n if (ttlSeconds <= 0) return;\n const key = buildKey(authorizerLogicalId, identityHash);\n map.set(key, { expiresAt: now() + ttlSeconds * 1000, result });\n },\n\n clear() {\n map.clear();\n },\n\n size() {\n sweep();\n return map.size;\n },\n };\n}\n","import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport * as path from 'node:path';\nimport { Command, Option } from 'commander';\nimport {\n appOptions,\n commonOptions,\n contextOptions,\n deprecatedRegionOption,\n parseContextOptions,\n parseAssumeRoleToken,\n effectiveAssumeRoleArn,\n type AssumeRoleOption,\n warnIfDeprecatedRegion,\n} from '../options.js';\nimport { getLogger } from '../../utils/logger.js';\nimport { applyRoleArnIfSet } from '../../utils/role-arn.js';\nimport { withErrorHandling } from '../../utils/error-handler.js';\nimport { listTargets } from '../../local/target-lister.js';\nimport { isInteractive, pickManyTargets } from '../../local/target-picker.js';\nimport { Synthesizer, type SynthesisOptions } from '../../synthesis/synthesizer.js';\nimport { resolveApp, resolveWatchConfig, type CdkWatchConfig } from '../config-loader.js';\nimport { createGlobMatcher } from '../../utils/glob-match.js';\nimport { readCdkPathOrUndefined } from '../cdk-path.js';\nimport {\n createLocalStateProvider,\n isCfnFlagPresent,\n rejectExplicitCfnStackWithMultipleStacks,\n resolveCfnFallbackRegion,\n type ExtraStateProviders,\n} from './local-state-source.js';\nimport {\n getEmbedConfig,\n setEmbedConfig,\n type CdkLocalEmbedConfig,\n} from '../../local/embed-config.js';\nimport type { StackInfo } from '../../synthesis/assembly-reader.js';\nimport type { TemplateResource } from '../../types/resource.js';\nimport type { StackState } from '../../types/state.js';\nimport {\n applyDeployedEnvFallback,\n substituteEnvVarsFromState,\n type PseudoParameters,\n type SubstitutionContext,\n} from '../../local/state-resolver.js';\nimport { derivePartitionAndUrlSuffix } from '../../local/ecs-task-resolver.js';\nimport { resolveRuntimeFileExtension, resolveRuntimeImage } from '../../local/runtime-image.js';\nimport { ensureDockerAvailable, pullImage } from '../../local/docker-runner.js';\nimport { architectureToPlatform, buildContainerImage } from '../../local/docker-image-builder.js';\nimport { parseEcrUri, pullEcrImage } from '../../local/ecr-puller.js';\nimport {\n derivePseudoParametersFromRegion,\n tryResolveImageFnJoin,\n} from '../../local/intrinsic-image.js';\nimport {\n AssetManifestLoader,\n getDockerImageBySourceHash,\n} from '../../assets/asset-manifest-loader.js';\nimport type { DockerImageAssetSource } from '../../types/assets.js';\nimport { discoverRoutes, type DiscoveredRoute } from '../../local/route-discovery.js';\nimport {\n discoverWebSocketApis,\n type DiscoveredWebSocketApi,\n} from '../../local/websocket-route-discovery.js';\nimport {\n attachWebSocketServer,\n handleManagementRequest,\n type AttachedWebSocketServer,\n} from '../../local/websocket-server.js';\nimport { buildMgmtEndpointEnvUrl } from '../../local/websocket-mgmt-api.js';\nimport { HOST_GATEWAY_MIN_VERSION, probeHostGatewaySupport } from '../../local/docker-version.js';\nimport { warnSsrfRiskyUri } from '../../local/rest-v1-integrations.js';\nimport {\n createContainerPool,\n type ContainerSpec,\n type ContainerPool,\n} from '../../local/container-pool.js';\nimport {\n startApiServer,\n readMtlsMaterialsFromDisk,\n type ServerState,\n type StartedApiServer,\n type MtlsServerConfig,\n} from '../../local/http-server.js';\nimport {\n availableApiIdentifiers,\n filterRoutesByApiIdentifiers,\n groupRoutesByServer,\n type ApiServerGroup,\n} from '../../local/api-server-grouping.js';\nimport { resolveEnvVars, type EnvOverrideFile } from '../../local/env-resolver.js';\nimport {\n extractEphemeralStorageMb,\n resolveLambdaLayers,\n type ResolvedLambdaLayer,\n} from '../../local/lambda-resolver.js';\nimport { materializeLayerFromArn } from '../../local/layer-arn-materializer.js';\nimport { matchStacks } from '../stack-matcher.js';\nimport {\n buildCorsConfigByApiId,\n buildCorsConfigFromCloudFrontChain,\n type CorsConfig,\n} from '../../local/cors-handler.js';\nimport {\n attachStageContext,\n buildStageMap,\n type ResolvedStage,\n} from '../../local/stage-resolver.js';\nimport { createFileWatcher, type FileWatcher } from '../../local/file-watcher.js';\nimport { type NextStateMaterial } from '../../local/reload-orchestrator.js';\nimport {\n attachAuthorizers,\n type AuthorizerInfo,\n type RouteWithAuth,\n} from '../../local/authorizer-resolver.js';\nimport { createAuthorizerCache } from '../../local/authorizer-cache.js';\nimport {\n buildCognitoJwksUrl,\n buildJwksUrlFromIssuer,\n createJwksCache,\n} from '../../local/cognito-jwt.js';\nimport { defaultCredentialsLoader, type CredentialsLoader } from '../../local/sigv4-verify.js';\nimport { singleFlight } from '../../utils/single-flight.js';\nimport {\n writeProfileCredentialsFile,\n type ProfileCredentialsFile,\n} from './local-profile-credentials-file.js';\n\ninterface LocalStartApiOptions {\n app?: string;\n output: string;\n verbose: boolean;\n region?: string;\n profile?: string;\n roleArn?: string;\n context?: string[];\n /** Bind port (default 0 = auto-allocate). */\n port: string;\n /** Bind host (default 127.0.0.1). */\n host: string;\n /** Stack pattern (single-stack apps auto-detect). */\n stack?: string;\n /**\n * Issue #55: serve the union of every stack's APIs (each on its own\n * port) instead of erroring out in a multi-stack app. Mutually\n * exclusive with a single-target selector (positional target /\n * `--stack` / explicit `--from-cfn-stack <name>`); the bare\n * `--from-cfn-stack` flag stays compatible (binds each routed stack\n * to its own CFn stack).\n */\n allStacks?: boolean;\n /** Pre-warm one container per Lambda at server boot. */\n warm: boolean;\n /** Pool size cap per Lambda (default 2, max 4). */\n perLambdaConcurrency: string;\n /** Skip docker pull for images. */\n pull: boolean;\n /** IP the host uses to bind/probe the RIE port (default 127.0.0.1). */\n containerHost: string;\n /** First Node.js inspector port; allocated contiguously per Lambda when set. */\n debugPortBase?: string;\n envVars?: string;\n /** D8.2: bare ARN (global) and/or `<LogicalId>=<arn>` (per-Lambda). */\n assumeRole?: AssumeRoleOption;\n /**\n * Issue #448: role to sts:AssumeRole before calling\n * `lambda:GetLayerVersion` for every literal-ARN entry in any\n * routed Lambda's `Properties.Layers`. Independent of `--assume-role`\n * (which scopes the handler's runtime AWS calls). Use only when the\n * dev credentials cannot read the layer (cross-account case);\n * AWS-published public layers like Lambda Powertools need no role.\n */\n layerRoleArn?: string;\n /** PR 8c: enable hot reload on `cdk.out/` + asset-dir changes. */\n watch: boolean;\n /** PR 8c: select a Stage by `StageName`; default is the first attached. */\n stage?: string;\n /**\n * Deprecated single-API alias for the positional `<targets...>` form.\n * Filters the discovered API surface to a single API by its logical id\n * (or, for Function URLs, the backing Lambda's logical id). Accepts\n * exactly ONE identifier; for a subset use the variadic positional\n * `cdkl start-api <id1> <id2>` form. Emits a deprecation warn on use.\n */\n api?: string;\n /**\n * Issue #606: alternative state source. Reads physical IDs from a\n * deployed CloudFormation stack via `ListStackResources`.\n * Commander maps:\n * - flag absent → `undefined`\n * - `--from-cfn-stack` (bare) → `true` (use the resolved stack name per routed stack)\n * - `--from-cfn-stack <name>` → `'<name>'` (single named CFn stack)\n */\n fromCfnStack?: string | boolean;\n /**\n * Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically\n * verified (foreign access-key-id, or no local AWS credentials) instead\n * of the default warn-and-pass. Default `false` (warn-and-pass). Mirrors\n * the `--strict-sigv4` CLI flag.\n */\n strictSigv4?: boolean;\n /**\n * Region of the state record to read. Used with --from-cfn-stack\n * as the CFn client region.\n */\n stackRegion?: string;\n /**\n * Path to a PEM-encoded CA bundle. Client certificates that don't\n * chain to one of these CAs are rejected at the TLS handshake.\n * mTLS is enabled when ALL THREE `--mtls-truststore` / `--mtls-cert` /\n * `--mtls-key` flags are set; a partial set is a CLI-parse error.\n */\n mtlsTruststore?: string;\n /** Server-side mTLS certificate (PEM). Self-signed is fine for local dev. */\n mtlsCert?: string;\n /** Server-side mTLS private key (PEM). Must match `--mtls-cert`. */\n mtlsKey?: string;\n /** Host-injected extra state-source flag fields. */\n [key: string]: unknown;\n}\n\n/**\n * `cdkl start-api` — long-running local HTTP server that maps\n * synthesized API routes to Lambda invocations against the AWS Lambda\n * Runtime Interface Emulator (Docker required).\n *\n * Modeled on `sam local start-api` but reusing cdk-local's synthesis /\n * route-discovery / container plumbing. PR 8a scope:\n * - REST v1 (AWS::ApiGateway::*) + HTTP API (AWS::ApiGatewayV2::*) +\n * Function URL (AWS::Lambda::Url).\n * - AWS_PROXY integrations only.\n *\n * PR 8b additions:\n * - Authorizers: REST v1 Lambda TOKEN/REQUEST + Cognito User Pool;\n * HTTP v2 Lambda REQUEST + JWT. Allow → claims/context flow into\n * `event.requestContext.authorizer`. Deny → 401/403 written without\n * invoking the route handler. Cognito / JWT verification falls back\n * to pass-through mode when the JWKS endpoint is unreachable.\n * - VPC-config Lambdas surface a startup warn line: the local\n * container does NOT get attached to the deployed VPC.\n *\n * PR 8c additions (issue #235):\n * - `--watch` enables hot reload on `cdk.out/` + asset-dir changes.\n * - HTTP API v2 OPTIONS preflight is intercepted when the API has a\n * `CorsConfiguration`; REST v1 CORS (Mock OPTIONS method) stays\n * out of scope.\n * - `event.stageVariables` is populated from the selected Stage's\n * `Variables` / `StageVariables` map. `--stage <name>` selects a\n * specific Stage by name; default is the first Stage attached.\n *\n * Still deferred: WebSocket APIs.\n *\n * See [docs/cli-reference.md](../../../docs/cli-reference.md) for the\n * full surface and out-of-scope items.\n */\n/**\n * Factory options for {@link createLocalStartApiCommand}.\n */\nexport interface CreateLocalStartApiCommandOptions {\n extraStateProviders?: ExtraStateProviders;\n /** Embed-time branding overrides for a host wrapping this factory. */\n embedConfig?: CdkLocalEmbedConfig;\n}\n\nasync function localStartApiCommand(\n targets: string[],\n options: LocalStartApiOptions,\n extraStateProviders: ExtraStateProviders | undefined\n): Promise<void> {\n const logger = getLogger();\n if (options.verbose) {\n logger.setLevel('debug');\n }\n\n // Resolve the API filter set: the variadic positional `<targets...>`\n // names the SUBSET of APIs to serve (each on its own port), with the\n // union of every supplied identifier served. The deprecated `--api`\n // flag is a SINGLE-identifier alias kept for one release cycle — every\n // invocation that goes through it emits a deprecation warn so users see\n // the migration path before the next major bump removes it. `--api`\n // conflicts with any positional target (they would name overlapping or\n // contradictory subsets).\n let apiFilters: string[] = [...targets];\n if (options.api !== undefined) {\n if (targets.length > 0) {\n throw new Error(\n `Cannot specify both positional target(s) ([${targets.join(', ')}]) and --api flag ('${options.api}'). ` +\n `Use one or the other. The positional form is preferred — '--api' is a deprecated alias.`\n );\n }\n logger.warn(\n `[deprecated] --api <id> will be removed in a future major release. Use the positional argument instead: '${getEmbedConfig().cliName} start-api <id>'.`\n );\n apiFilters = [options.api];\n }\n\n // Bare `start-api` (no positional targets, no `--api`, no `--all-stacks`)\n // keeps its long-standing \"serve every discovered API\" default. In a TTY\n // we open a MULTI-SELECT picker (every API pre-selected) so the user can\n // press Enter to serve all or deselect to pick a subset; in a non-TTY\n // (CI, pipe) we serve all without prompting — start-api's one intentional\n // asymmetry vs invoke / run-task, which legitimately have no \"serve all\"\n // default. The actual prompt runs once inside `synthesizeAndBuild` (it\n // needs the synthesized API list), with `--watch` reloads reusing the\n // first selection via `interactivePicked`.\n const shouldPromptBare = shouldPromptBareMultiSelect(\n apiFilters,\n options.allStacks,\n isInteractive()\n );\n // Effective selectors driving synth stack-prefix inference: the\n // positional targets, or (after the bare multi-select fires) the picked\n // identifiers. Set inside `synthesizeAndBuild` when the picker runs.\n let effectiveTargets = [...apiFilters];\n const interactivePicked = { value: false };\n\n // Issue #55: `--all-stacks` serves every stack's API as a union, so it\n // cannot be combined with a selector that names a target subset.\n const allStacksConflictList = allStacksConflicts(options, targets, apiFilters);\n if (allStacksConflictList.length > 0) {\n throw new Error(\n `--all-stacks serves every stack's API and cannot be combined with a target subset selector (${allStacksConflictList.join(', ')}). ` +\n `Drop --all-stacks to target specific APIs, or drop the selector to serve them all. ` +\n `The bare --from-cfn-stack flag (no value) IS compatible with --all-stacks.`\n );\n }\n\n warnIfDeprecatedRegion(options);\n await applyRoleArnIfSet({ roleArn: options.roleArn, region: options.region });\n\n await ensureDockerAvailable();\n\n const appCmd = resolveApp(options.app);\n if (!appCmd) {\n throw new Error(\n `No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add \"app\" to cdk.json.`\n );\n }\n\n const overrides = readEnvOverridesFile(options.envVars);\n const debugPortBase = options.debugPortBase ? parseDebugPort(options.debugPortBase) : undefined;\n const perLambdaConcurrency = parsePerLambdaConcurrency(options.perLambdaConcurrency);\n // Track every tmpdir created by `materializeInlineCode` so the\n // graceful-shutdown path removes them. Long-running servers (this\n // command) would otherwise leak one tmpdir per inline-`Code.ZipFile`\n // Lambda per server invocation. Hot reload writes new tmpdirs into\n // the same set so the shutdown path is the single owner of cleanup.\n const inlineTmpDirs = new Set<string>();\n // PR 6 (#232): track every tmpdir created by layer merging too —\n // `materializeLambdaLayers(...)` produces one merged tmpdir per\n // Lambda whose `Properties.Layers` contains 2+ entries (single-\n // layer Lambdas bind-mount the layer's asset dir directly).\n // Cleaned up alongside `inlineTmpDirs` in `shutdown(...)`. Hot\n // reload (PR 8c) reuses this same set across reload firings; on\n // each `synthesizeAndBuild` re-run we record the new merged\n // tmpdirs (the previous iteration's entries stay behind until\n // shutdown — a follow-up PR can prune them per-reload, but the\n // shutdown path is the single owner of cleanup so leaks are\n // bounded by server lifetime).\n const layerTmpDirs = new Set<string>();\n // Synthesized AWS shared credentials file bind-mounted into every Lambda\n // container so handlers\n // using `fromIni({ profile })` explicitly resolve to the same creds as\n // the default chain. Created ONCE at server boot (NOT on hot reload —\n // re-resolving profile creds is fine for env vars but rewriting the\n // file mid-flight would race with running containers' SDK reads).\n // Disposed in the cleanup chain. Undefined when `--profile` is unset.\n let profileCredsFile: ProfileCredentialsFile | undefined;\n\n // PR 8b: per-server-lifecycle caches. Constructed once at server\n // startup; persisted across hot reloads (PR 8c) so authorizer\n // verdicts and JWKS keys aren't re-fetched on every reload. The\n // jwksWarnedUrls Set ensures the pass-through warn fires at most\n // ONCE per JWKS URL per server lifecycle.\n const authorizerCache = createAuthorizerCache();\n const jwksCache = createJwksCache();\n const jwksWarnedUrls = new Set<string>();\n // #447: SigV4 verifier state for `AuthorizationType: 'AWS_IAM'` routes.\n // The credentials loader is constructed eagerly but the credential\n // chain itself is only hit on the first IAM-protected request — see\n // `defaultCredentialsLoader` for the caching contract.\n let sigV4CredentialsLoader: CredentialsLoader | undefined;\n const sigV4WarnedForeignIds = new Set<string>();\n // One-shot guard for the `--from-cfn-stack <name>` redundancy tip\n // (improvement A). `synthesizeAndBuild` runs on every `--watch` hot\n // reload firing, so without this flag the tip would re-emit on every\n // reload — noisy. The flag flips on the first emission and stays\n // `true` for the rest of the server lifetime; subsequent reloads see\n // the gate and skip the emission. Per-`localStartApiCommand`-invocation\n // (new server boot starts with a fresh `false`).\n const fromCfnTipEmitted: { value: boolean } = { value: false };\n // One-shot guard for the per-identifier \"did not match; ignored\" warning\n // from the target-subset resolver. `synthesizeAndBuild` re-runs on every\n // `--watch` hot reload firing, so without this set a typo'd identifier\n // would re-warn on every file change. Each unmatched id is warned at most\n // ONCE per `start-api` run; subsequent reloads see the id already in the\n // set and skip it. Per-`localStartApiCommand`-invocation (new server boot\n // starts with a fresh empty set).\n const unmatchedTargetWarned = new Set<string>();\n\n /**\n * One synth + discover + build pass. Returns the next-state\n * material. Reused on initial boot AND every hot-reload firing.\n * Failures bubble up — the orchestrator catches them and keeps the\n * old state; the initial boot lets them propagate so the CLI exits\n * with a clear error before \"Server listening\" is ever printed.\n *\n * PR 8b: also runs `attachAuthorizers` after route discovery so the\n * resulting `RouteWithAuth[]` carries every route's authorizer info.\n * Hot reload picks up authorizer-config changes via this re-run.\n */\n const synthesizeAndBuild = async (): Promise<NextStateMaterial> => {\n logger.info('Synthesizing CDK app...');\n const synthesizer = new Synthesizer();\n const context = parseContextOptions(options.context);\n const synthOpts: SynthesisOptions = {\n app: appCmd,\n output: options.output,\n ...(options.region && { region: options.region }),\n ...(options.profile && { profile: options.profile }),\n ...(Object.keys(context).length > 0 && { context }),\n };\n const { stacks } = await synthesizer.synthesize(synthOpts);\n\n // Bare multi-select — runs ONCE (the first synth), guarded so\n // `--watch` reloads reuse the first selection rather than re-prompting.\n // Every API is pre-selected (Enter = serve all); the user deselects to\n // pick a subset. Sets both `apiFilters` (route filter set) and\n // `effectiveTargets` (stack inference) so the picked subset drives the\n // rest of the pass.\n if (shouldPromptBare && !interactivePicked.value) {\n const apis = listTargets(stacks).apis;\n if (apis.length === 0) {\n throw new Error(\n `No APIs found in this CDK app to choose from. Run \\`${getEmbedConfig().cliName} list\\` to see what is available.`\n );\n }\n const picked = await pickManyTargets('Select APIs to serve', apis);\n apiFilters = picked;\n effectiveTargets = picked;\n interactivePicked.value = true;\n }\n\n // When the user passes an explicit `--from-cfn-stack <name>` AND has\n // not also passed `--stack`, infer the synth target from the CFn\n // stack name — typical CDK apps deploy each stack under its own\n // stack-name, so the same value selects the synth target unambiguously.\n // `--from-cfn-stack` without a value (bare flag => `true`) is left to\n // the regular single-stack auto-pick path.\n const cfnStackFallback =\n typeof options.fromCfnStack === 'string' ? options.fromCfnStack : undefined;\n // Improvement B (extended to subsets): synth stack-prefix inference.\n // `cdkl start-api MyStack/MyApi` (no `--stack`, no `--from-cfn-stack`)\n // extracts `MyStack` from the target's `<StackName>/<construct>` shape\n // and uses it as a third fallback for stack selection. With MULTIPLE\n // targets we only keep the optimization when they all share ONE stack\n // prefix; targets spanning different stacks (or any bare-logical-id\n // target without a `/` separator) leave this undefined so the synth\n // scope falls back to ALL stacks (item 5) and the union is filtered\n // down afterwards by `apiFilters`.\n const targetStackPrefix = deriveSynthStackPrefix(effectiveTargets);\n const targetStacks = pickTargetStacks(\n stacks,\n options.stack,\n cfnStackFallback,\n targetStackPrefix,\n // Fall back to synthesizing ALL stacks when an explicit subset spans\n // (or might span) multiple stacks and no other selector pins one.\n options.allStacks || shouldSynthAllStacks(effectiveTargets, options.stack, cfnStackFallback)\n );\n if (targetStacks.length === 0) {\n throw new Error(\n 'No stacks matched. Pass --stack <name> (or --from-cfn-stack <name>) or run from a single-stack app.'\n );\n }\n\n // Improvement A: when the user passed `--from-cfn-stack <explicit>`\n // AND that value already equals the routed stack's `stackName`, the\n // explicit value is redundant — the bare-flag form auto-resolves to\n // the same value. Surface a single info-level tip so the user knows\n // they can drop the value next time. Skipped on multi-stack runs\n // (too many edge cases to keep the message useful).\n //\n // `synthesizeAndBuild` re-runs on every `--watch` hot reload firing,\n // so the emission is gated by `fromCfnTipEmitted.value` (one-shot per\n // server lifetime) — see `tryEmitFromCfnRedundancyTipOnce`.\n const routedStackNames = targetStacks.map((s) => s.stackName);\n tryEmitFromCfnRedundancyTipOnce(\n options.fromCfnStack,\n routedStackNames,\n fromCfnTipEmitted,\n (routedStackName) => {\n logger.info(\n `tip: --from-cfn-stack value matches the routed stack name (${routedStackName}); you can omit the value: \\`${getEmbedConfig().cliName} start-api ... --from-cfn-stack\\` (bare flag) resolves to the same value.`\n );\n }\n );\n\n const routes = discoverRoutes(targetStacks);\n // #462: WebSocket APIs (ProtocolType: 'WEBSOCKET') are discovered\n // through a sibling pipeline so the resulting routes (keyed by\n // RouteKey rather than by method+path) stay structurally separate\n // from the HTTP / REST / Function URL `DiscoveredRoute[]`. Errors\n // are aggregated into the same boot-time warn pass so a malformed\n // WebSocket API does not abort sibling HTTP API boot.\n const wsDiscovery = discoverWebSocketApis(targetStacks);\n if (wsDiscovery.errors.length > 0) {\n for (const e of wsDiscovery.errors) {\n logger.warn(`WebSocket discovery: ${e}`);\n }\n }\n const webSocketApis = wsDiscovery.apis;\n if (routes.length === 0 && webSocketApis.length === 0) {\n throw new Error(\n `No supported API routes were discovered. ${getEmbedConfig().cliName} start-api supports AWS::ApiGateway::* (REST v1), AWS::ApiGatewayV2::* (HTTP + WebSocket), and AWS::Lambda::Url (Function URL) with AWS_PROXY integrations only.`\n );\n }\n\n // PR 8c: stage selection + variable injection. Build the per-API\n // Stage map for every target stack and attach it to the routes.\n // Stage selection is `--stage <name>` global override, otherwise\n // first-attached default. The CLI surfaces a warn line when\n // `--stage` was passed and at least one API doesn't have a Stage\n // with that name.\n const stageMap = new Map<string, ResolvedStage>();\n for (const stack of targetStacks) {\n const m = buildStageMap(stack.template, options.stage);\n for (const [k, v] of m) stageMap.set(k, v);\n }\n if (options.stage) {\n // Walk the routes looking for HTTP API v2 / REST v1 routes whose\n // API isn't in `stageMap` (i.e. the API had no Stage with the\n // override name). One warn per such API, deduplicated.\n const missingApis = new Set<string>();\n for (const r of routes) {\n if (!r.apiLogicalId) continue;\n if (!stageMap.has(r.apiLogicalId)) missingApis.add(r.apiLogicalId);\n }\n for (const apiId of missingApis) {\n logger.warn(\n `--stage '${options.stage}' did not match any Stage on API '${apiId}'; routes on that API will get stageVariables: null.`\n );\n }\n }\n attachStageContext(routes, stageMap);\n\n // PR 8b: attach authorizer info to every route. Routes without an\n // authorizer pass through as `{route, authorizer: undefined}`.\n // Routes referencing an unsupported authorizer kind hard-fail here.\n let routesWithAuth = attachAuthorizers(targetStacks, routes);\n\n // Target subset filter — restrict the discovered surface to the UNION\n // of the supplied identifiers. Useful when the user wants exactly a\n // few servers (e.g. to free other ports, or to focus testing on a\n // couple of APIs / Function URLs).\n //\n // The pure resolver enforces strict multi-stack bare-id rejection +\n // empty-union rejection (both THROW) and returns the surviving union\n // plus the identifiers that matched nothing. Available identifiers are\n // computed once inside it (not per-id) for the O(N·M) bound.\n if (apiFilters.length > 0) {\n const { filtered, unmatched } = resolveApiTargetSubset(\n routesWithAuth,\n apiFilters,\n targetStacks.map((s) => s.stackName)\n );\n // Surface any individual identifier that matched nothing (the union\n // still has routes from its siblings, so the run continues) — a\n // single typo in a multi-target list otherwise silently serves a\n // smaller set than the user asked for. Gated one-shot per identifier\n // (`unmatchedTargetWarned`) so a `--watch` hot reload doesn't re-warn\n // the same typo on every file change.\n if (unmatched.length > 0) {\n const available = availableApiIdentifiers(routesWithAuth).join(', ') || '(none)';\n for (const id of unmatched) {\n if (unmatchedTargetWarned.has(id)) continue;\n unmatchedTargetWarned.add(id);\n logger.warn(\n `Target '${id}' did not match any discovered API; it is ignored. Available identifiers: ${available}.`\n );\n }\n }\n routesWithAuth = filtered;\n }\n\n // PR 8c: per-API CORS config. HTTP API v2's `CorsConfiguration` +\n // Function URL's own `Cors` (issue #644) + CORS borrowed from\n // CloudFront ResponseHeadersPolicy when a CloudFront Distribution\n // fronts the Function URL (issue #646 — production-correct CDK\n // pattern where CORS is on the edge, not the origin). REST v1\n // OPTIONS Mock integrations stay out of scope.\n //\n // Merge order: direct map last → Function URL's own `Cors` block\n // wins over a CloudFront-borrowed config when both exist (matches\n // AWS semantics: Function URL's own headers apply at the Lambda\n // boundary; we only have one slot locally, so closer-to-Lambda\n // wins). The two rarely coexist in practice.\n const corsConfigByApiId = new Map<string, CorsConfig>();\n for (const stack of targetStacks) {\n const fromCloudFront = buildCorsConfigFromCloudFrontChain(stack.template);\n for (const [k, v] of fromCloudFront) corsConfigByApiId.set(k, v);\n const direct = buildCorsConfigByApiId(stack.template);\n for (const [k, v] of direct) corsConfigByApiId.set(k, v);\n }\n\n // `--from-state` / `--from-cfn-stack` (issue #606): load deployed\n // state for every routed stack once per synth (= initial boot AND\n // every hot-reload firing). We do this outside the per-Lambda loop\n // so a stack with N reachable Lambdas only pays one state-load\n // round-trip. Pseudo parameters are also resolved once per stack —\n // STS GetCallerIdentity is account-wide so the bag is identical\n // across same-region stacks, but the partition / URL suffix can\n // differ if stacks span partitions. Per-stack failures degrade to\n // warn-and-fall-back (the PR 1 behavior is preserved) so a missing\n // or unreadable state file never aborts the server boot.\n const stateSourceActive =\n isCfnFlagPresent(options) || hasExtraStateProviderActive(options, extraStateProviders);\n const stateByStack = stateSourceActive\n ? await loadStateForRoutedStacks(\n targetStacks,\n routes,\n routesWithAuth,\n options,\n extraStateProviders\n )\n : new Map<string, StackStateBundle>();\n\n // Issue #654: resolve `--profile <p>` to a concrete credential set\n // ONCE at boot for forwarding into every Lambda container that\n // doesn't have its own `--assume-role`. Drives the SDK's default\n // credential provider chain (SSO / IAM Identity Center / fromIni /\n // role-assumption profiles all handled uniformly — same chain that\n // already resolves `--profile` for the host's own AWS SDK clients).\n // Resolved here so a stack with N Lambdas pays one STS round-trip.\n // SSO temp creds typically last 1h+; long-running `--watch` sessions\n // outliving the creds need a cdk-local restart (auto-refresh deferred,\n // see issue #654).\n const profileCredentials = options.profile\n ? await resolveProfileCredentials(options.profile)\n : undefined;\n\n // When `--profile <p>` is set, ALSO synthesize a shared AWS\n // credentials file with the\n // resolved creds under `[<p>]` and bind-mount it into every Lambda\n // container at `/cdk-local-aws/credentials`. This lets handlers that\n // use `fromIni({ profile: '<p>' })` explicitly inside their code\n // (instead of the default credential chain) resolve to the same\n // creds locally. The default-chain path (most handlers) keeps\n // working through the existing `AWS_*` env-var injection — the\n // file is the additive layer for the explicit-profile case.\n //\n // Caveat: under `--watch` hot reload, `profileCredentials` re-resolves\n // and the new env vars flow to the next cold start, but the on-disk\n // file is NOT re-written. Long-running sessions whose SSO temp creds\n // expire need a cdk-local restart (matches the auto-refresh-deferred\n // stance for env vars).\n //\n // Assigned to outer-scope `profileCredsFile` so the cleanup chain\n // disposes the file at shutdown. Only ever assigned on the FIRST\n // synthesizeAndBuild (initial server boot) — hot reload sees the\n // variable already set and skips re-writing per the caveat above.\n if (options.profile && profileCredentials && !profileCredsFile) {\n profileCredsFile = await writeProfileCredentialsFile(options.profile, profileCredentials);\n }\n\n // Build the per-Lambda spec map. Every reachable logical ID is\n // resolved to its asset / inline code, env vars, optional STS creds\n // (--assume-role), optional --debug-port reservation. The container\n // pool then knows everything it needs to lazy-start a fresh one.\n // Authorizer Lambdas are also pooled — they're invoked just like\n // route handlers (PR 8b).\n const lambdaIds = uniqueLambdaIds(routes, routesWithAuth, webSocketApis);\n const specs = new Map<string, ContainerSpec>();\n for (let i = 0; i < lambdaIds.length; i++) {\n const logicalId = lambdaIds[i]!;\n const spec = await buildContainerSpec({\n logicalId,\n stacks: targetStacks,\n overrides,\n assumeRole: options.assumeRole,\n containerHost: options.containerHost,\n ...(debugPortBase !== undefined && { debugPort: debugPortBase + i }),\n stsRegion: options.region ?? process.env['AWS_REGION'] ?? process.env['AWS_DEFAULT_REGION'],\n inlineTmpDirs,\n layerTmpDirs,\n stateByStack,\n skipPull: options.pull === false,\n ...(options.profile !== undefined && { profile: options.profile }),\n ...(options.layerRoleArn !== undefined && { layerRoleArn: options.layerRoleArn }),\n ...(profileCredentials && { profileCredentials }),\n ...(profileCredsFile && { profileCredsFile }),\n ...(profileCredentials?.region && { profileRegion: profileCredentials.region }),\n ...(options.stackRegion && { stackRegionOverride: options.stackRegion }),\n });\n specs.set(logicalId, spec);\n }\n\n // Pull every distinct base image up front so the first request\n // doesn't pay the layer-pull cost. Mirrors `cdkl invoke`'s\n // pull pass. Only the ZIP branch needs a base-image pull here —\n // IMAGE-variant specs already have their per-Lambda image\n // resolved by `buildContainerSpec` (`resolveContainerImageForStartApi`\n // ran the local build or ECR pull before this point), so the\n // ContainerPool can `docker run` against it without any further\n // pull step.\n const distinctImages = new Set<string>();\n for (const spec of specs.values()) {\n if (spec.kind === 'zip') {\n distinctImages.add(resolveRuntimeImage(spec.lambda.runtime));\n }\n }\n for (const image of distinctImages) {\n await pullImage(image, options.pull === false);\n }\n\n return {\n routes: routesWithAuth,\n specs,\n corsConfigByApiId,\n webSocketApis,\n stacks: targetStacks,\n };\n };\n\n /**\n * Helper: build a {@link ContainerPool} from a spec map and tag it\n * with the spec map (via the non-enumerable `__cdklSpecs` property)\n * so the reload orchestrator can compute spec diffs.\n */\n const buildPool = (specs: Map<string, ContainerSpec>): ContainerPool => {\n const pool = createContainerPool(specs, {\n perLambdaConcurrency,\n skipPull: options.pull === false,\n });\n Object.defineProperty(pool, '__cdklSpecs', {\n value: specs,\n enumerable: false,\n configurable: true,\n });\n return pool;\n };\n\n // Initial boot.\n const initialMaterial = await synthesizeAndBuild();\n\n // PR 8b: pre-warm JWKS for Cognito / JWT authorizers so the first\n // request doesn't pay the fetch latency. Failures fall through to\n // pass-through mode with the warn line documented in cognito-jwt.ts.\n await prewarmJwks(initialMaterial.routes, jwksCache);\n\n // PR 8b: VPC-config Lambdas warn at startup. cdk-local does NOT block\n // these routes, but the developer should know the local container\n // reaches external services via the host's network rather than\n // through the deployed VPC's NAT / private subnets. Re-runs on hot\n // reload would be noisy; we emit this once at initial boot only.\n warnVpcConfigLambdas(initialMaterial.routes, initialMaterial.stacks ?? []);\n\n // #447: AWS_IAM-protected routes warn at startup. The local server\n // verifies SigV4 signatures only — IAM policy evaluation (resource /\n // action / condition) is NOT emulated.\n //\n // The credentials loader is ALWAYS constructed at boot (not gated on\n // the initial template having IAM routes) so that hot-reload\n // (`--watch`) paths that ADD a new IAM route after boot have the\n // loader already wired up. The loader is internally lazy\n // (`defaultCredentialsLoader()` memoizes the actual STSClient +\n // credential resolution until first call), so unused boots pay zero\n // cost. Pre-fix the loader was conditionally constructed at this\n // point only when the initial template had IAM routes, which caused\n // post-hot-reload IAM routes to hit the http-server's defensive\n // \"no SigV4 credentials loader configured — denying\" deny path with\n // no explanation. PR #484 review MAJOR.\n sigV4CredentialsLoader = defaultCredentialsLoader();\n warnIamRoutes(initialMaterial.routes);\n\n // RIE invoke timeout: 2x the slowest Lambda's Timeout, floor 30s.\n let maxTimeoutSec = 0;\n for (const spec of initialMaterial.specs.values()) {\n if (spec.lambda.timeoutSec > maxTimeoutSec) maxTimeoutSec = spec.lambda.timeoutSec;\n }\n const rieTimeoutMs = Math.max(30_000, maxTimeoutSec * 2 * 1000);\n\n const basePort = parseInt(options.port, 10);\n if (!Number.isFinite(basePort) || basePort < 0 || basePort > 65535) {\n throw new Error(`--port must be 0..65535 (got ${options.port}).`);\n }\n\n // mTLS resolution: all-or-none. When any of the three flags is set,\n // ALL THREE must be set — partial flag sets are rejected at CLI-parse\n // time so the server never boots in a half-configured state. The TLS\n // handshake itself (in `https.createServer({requestCert: true,\n // rejectUnauthorized: true, ca, cert, key})`) enforces the\n // client-cert chain check against the CA bundle — there is no\n // per-request validation in cdk-local's code path.\n const mtlsConfig: MtlsServerConfig | undefined = resolveMtlsConfig(options);\n if (mtlsConfig) {\n logger.info(\n 'mTLS enabled: client certificates required (chain check against --mtls-truststore at TLS handshake).'\n );\n }\n\n // Issue #260: one HTTP server per API. Group the routes by API surface\n // (HTTP API logical id / REST API logical id / Function URL backing\n // Lambda) and launch one `startApiServer` per group. Each server gets\n // its own ContainerPool (filtered to the Lambdas reachable from that\n // group's routes) so authorizers, CORS configs, and stage variables\n // are scoped to the correct API and never bleed across them.\n const initialGroups = groupRoutesByServer(initialMaterial.routes);\n // basePort is the FIRST server's port; subsequent servers get\n // basePort+1, basePort+2, ... When basePort is 0 every server\n // auto-allocates. Auto-allocation is fine even across multiple\n // servers because the OS picks distinct ports.\n const servers: BootedApiServer[] = [];\n let nextPort = basePort;\n for (const group of initialGroups) {\n const groupSpecs = filterSpecsForGroup(group, initialMaterial.specs);\n const groupPool = buildPool(groupSpecs);\n const groupState: ServerState = {\n routes: group.routes,\n pool: groupPool,\n corsConfigByApiId: initialMaterial.corsConfigByApiId,\n };\n // Optional pre-warm: one container per Lambda, in parallel.\n if (options.warm) {\n logger.info(`Pre-warming ${groupSpecs.size} container(s) for ${group.displayName}...`);\n const handles = await Promise.allSettled(\n [...groupSpecs.keys()].map((id) => groupPool.acquire(id))\n );\n for (const result of handles) {\n if (result.status === 'fulfilled') {\n groupPool.release(result.value);\n } else {\n logger.warn(\n `Pre-warm failed for one Lambda in ${group.displayName} (cold start cost will apply on first request): ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`\n );\n }\n }\n }\n const defaultRegion =\n options.region ?? process.env['AWS_REGION'] ?? process.env['AWS_DEFAULT_REGION'] ?? undefined;\n const started = await startApiServer({\n state: groupState,\n rieTimeoutMs,\n host: options.host,\n ...(mtlsConfig && { mtls: mtlsConfig }),\n // Increment per server; basePort=0 leaves every server on auto-alloc.\n port: basePort === 0 ? 0 : nextPort,\n authorizerCache,\n jwksCache,\n jwksWarnedUrls,\n sigV4CredentialsLoader,\n sigV4WarnedForeignIds,\n sigV4Strict: options.strictSigv4 === true,\n // #458: surfaces as the per-route fallback region for HTTP API\n // v2 service integrations. Per-request `RequestParameters.Region`\n // overrides this — matches AWS API Gateway behavior.\n ...(defaultRegion && { defaultRegion }),\n });\n servers.push({ group, server: started });\n if (basePort !== 0) nextPort += 1;\n }\n\n // #462: WebSocket API server boot loop. One HTTP server per\n // discovered WebSocket API; each server hosts an upgrade-event\n // listener via `attachWebSocketServer` AND a pre-pass HTTP handler\n // for the `@connections/<id>` data plane. Lambda containers in the\n // pool are injected with `AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI`\n // pointing at the same port so `apigatewaymanagementapi:PostToConnection`\n // from inside the handler lands on cdk-local's local endpoint.\n const wsServers: BootedWebSocketServer[] = [];\n const initialWsApis = initialMaterial.webSocketApis ?? [];\n warnUnsupportedWebSocketApis(initialWsApis, logger);\n\n // Issue #527 M2: probe Docker server version ONCE per session before\n // any WebSocket API attaches. The `--add-host=host.docker.internal:host-gateway`\n // mapping cdk-local injects requires Docker 20.10+; older daemons silently\n // fail with `ENOTFOUND host.docker.internal` at SDK-call time. Only\n // probe when at least one ATTACHABLE WebSocket API exists — HTTP /\n // REST-only sessions don't use the host-gateway mapping and shouldn't\n // pay the docker subprocess hop. `unsupported`-tagged APIs are also\n // skipped (they never enter the attach loop).\n const attachableWsApis = initialWsApis.filter((api) => !api.unsupported);\n if (attachableWsApis.length > 0) {\n const probe = await probeHostGatewaySupport();\n if (!probe.supported) {\n throw new Error(\n `${getEmbedConfig().cliName} start-api requires Docker ${HOST_GATEWAY_MIN_VERSION.major}.${HOST_GATEWAY_MIN_VERSION.minor}+ ` +\n `for WebSocket API support (--add-host=host.docker.internal:host-gateway needs the 20.10 host-gateway alias). ` +\n `Detected server version: ${probe.rawVersion || '<empty — daemon unreachable or output stripped>'}. ` +\n `Upgrade Docker, or remove the WebSocket API from this app to fall back to HTTP-only start-api.`\n );\n }\n if (probe.parsed === null) {\n logger.warn(\n `Docker server version \"${probe.rawVersion}\" did not match the canonical \"<major>.<minor>\" shape; ` +\n `assuming host-gateway support. If WebSocket containers fail to reach the local server, ` +\n `verify your Docker-compatible CLI honors --add-host=host.docker.internal:host-gateway.`\n );\n }\n }\n\n for (const api of initialWsApis) {\n // Skip APIs flagged unsupported at discovery — typical cause is a\n // non-NONE `AuthorizationType` on `$connect` (cdkl v1 does not\n // emulate WebSocket authorizers; admitting unauthenticated clients\n // would diverge from AWS-deployed behavior). The warn fired above\n // names the affected routes; here we just skip the attach loop so\n // no upgrade is accepted on this API.\n if (api.unsupported) continue;\n const wsLambdaIds = new Set(api.routes.map((r) => r.targetLambdaLogicalId));\n const wsSpecs = new Map<string, ContainerSpec>();\n for (const id of wsLambdaIds) {\n const spec = initialMaterial.specs.get(id);\n if (spec) wsSpecs.set(id, spec);\n }\n if (wsSpecs.size === 0) {\n logger.warn(\n `WebSocket API ${api.declaredAt}: no resolvable Lambda backing routes; skipping.`\n );\n continue;\n }\n const wsPool = buildPool(wsSpecs);\n const wsState: ServerState = {\n routes: [],\n pool: wsPool,\n corsConfigByApiId: new Map(),\n };\n // The HTTP server hosting the WebSocket also serves the\n // management-API `@connections/<id>` calls. Build the\n // {@link AttachedWebSocketServer} AFTER the http server is bound\n // so we know the port — needed to inject\n // `AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI` into every WebSocket\n // Lambda's env map BEFORE the pool starts the first container.\n const wsApiPath = `/${api.stage}`;\n // Forward-decl the registry pointer so the preDispatch closure\n // can read it once we attach below.\n let registryRef: AttachedWebSocketServer | undefined;\n const started = await startApiServer({\n state: wsState,\n rieTimeoutMs,\n host: options.host,\n port: basePort === 0 ? 0 : nextPort,\n authorizerCache,\n jwksCache,\n jwksWarnedUrls,\n sigV4WarnedForeignIds,\n sigV4Strict: options.strictSigv4 === true,\n preDispatch: async (req, res) => {\n if (!registryRef) return false;\n return handleManagementRequest(req, res, registryRef.registry);\n },\n });\n const attached = attachWebSocketServer({\n httpServer: started.server,\n pool: wsPool,\n rieTimeoutMs,\n apis: [{ api, apiPath: wsApiPath }],\n });\n registryRef = attached;\n\n // Inject the management-API endpoint URL into every WebSocket\n // Lambda's container env BEFORE any cold-start. The container\n // pool reads `spec.env` at `acquire()` time, so mutating the\n // shared map after the server is bound but before the first\n // request hits the route is correct. The mutation is per-Lambda:\n // only Lambdas backing this WebSocket API see the override; the\n // sibling HTTP / REST Lambdas' env maps stay untouched.\n //\n // Placeholder credentials: the AWS SDK v3 client constructor\n // requires SOME credentials to sign requests, even when the\n // endpoint override points at a local server that ignores\n // SigV4. When neither `--assume-role` nor inherited\n // `AWS_ACCESS_KEY_ID` etc. are set, the SDK errors with\n // `CredentialsProviderError: Could not load credentials from\n // any providers`. We populate fake credentials per the\n // ecs-network.ts metadata-sidecar precedent — cdk-local's\n // `@connections` handler doesn't verify SigV4, so the values\n // are opaque.\n //\n // Endpoint hostname: the URL is consumed INSIDE the Lambda\n // container, so `127.0.0.1` would be the container's own\n // loopback — not the host. On Docker Desktop (macOS / Windows)\n // `host.docker.internal` resolves to the host. Linux native\n // dockerd doesn't expose this hostname by default but Docker\n // 20.10+ supports `--add-host=host.docker.internal:host-gateway`\n // at runtime; we forward that flag via `extraHostMappings` on\n // the container spec for every WebSocket Lambda so the URL\n // always resolves on every platform. The host port is the\n // local server's bound port.\n //\n // Stage path: the URL INCLUDES `/${api.stage}` to match the\n // AWS-docs-canonical handler shape. The deployed\n // apigatewaymanagementapi endpoint URL is\n // `https://<api-id>.execute-api.<region>.amazonaws.com/<stage>`,\n // so SDK clients built from `domainName + stage` produce\n // `POST /<stage>/@connections/<id>`. Mirror that exactly for the\n // env-var override path so handlers that build the SDK client\n // with `new ApiGatewayManagementApiClient({})` (and let the\n // SDK pick up the env override) AND handlers that build it\n // with the explicit `{endpoint: 'https://' + domainName + '/' + stage}`\n // shape both work without per-handler code differences.\n // The pre-fix endpoint dropped `/${stage}` and any SDK call\n // that included the stage segment hit a 404 against the local\n // parser.\n const mgmtEndpoint = buildMgmtEndpointEnvUrl('host.docker.internal', started.port, api.stage);\n const hostGatewayMapping: { host: string; ip: string }[] = [\n { host: 'host.docker.internal', ip: 'host-gateway' },\n ];\n for (const id of wsLambdaIds) {\n const spec = initialMaterial.specs.get(id);\n if (!spec) continue;\n spec.env['AWS_ENDPOINT_URL_APIGATEWAYMANAGEMENTAPI'] = mgmtEndpoint;\n if (!spec.env['AWS_ACCESS_KEY_ID']) {\n spec.env['AWS_ACCESS_KEY_ID'] = 'AKIAIOSFODNN7EXAMPLE';\n spec.env['AWS_SECRET_ACCESS_KEY'] = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY';\n }\n // Ensure the SDK has a region — apigatewaymanagementapi\n // clients refuse to instantiate without one.\n if (!spec.env['AWS_REGION']) {\n spec.env['AWS_REGION'] =\n options.region ??\n process.env['AWS_REGION'] ??\n process.env['AWS_DEFAULT_REGION'] ??\n 'us-east-1';\n }\n // Mutate the spec to include host.docker.internal mapping so\n // the Lambda's `apigatewaymanagementapi:PostToConnection` URL\n // resolves to the host's cdk-local server on every OS (Linux native\n // dockerd does NOT auto-expose `host.docker.internal`; macOS /\n // Windows Docker Desktop does).\n const merged = [...(spec.extraHosts ?? []), ...hostGatewayMapping];\n spec.extraHosts = merged;\n }\n\n wsServers.push({ api, server: started, attached, apiPath: wsApiPath });\n if (basePort !== 0) nextPort += 1;\n }\n\n printPerServerRouteTables(servers);\n const allRoutes = servers.flatMap((s) => s.group.routes.map((r) => r.route));\n warnUnsupportedRoutes(allRoutes, logger);\n warnSsrfRiskyIntegrations(allRoutes, logger);\n logger.info(\n `Per-Lambda concurrency: ${perLambdaConcurrency} (override with --per-lambda-concurrency)`\n );\n // D8.4 — load-bearing: verify.sh greps for this exact prefix.\n // Emit one line per server so verify.sh / users can match each API to\n // its port. When mTLS is active, the scheme flips to `https://` and\n // verify.sh / users have to pass `--cacert` + `--cert` + `--key` to\n // curl; both schemes share the \"Server listening on \" prefix so the\n // verify.sh marker scan is unchanged.\n for (const { group, server } of servers) {\n process.stdout.write(\n `Server listening on ${server.scheme}://${server.host}:${server.port} (${group.displayName})\\n`\n );\n }\n // #462: emit one banner per WebSocket server. The protocol prefix\n // is `ws://` / `wss://` rather than `http://` / `https://` so users\n // copy-paste the URL straight into `wscat -c <URL>` / browser\n // `new WebSocket(url)`.\n for (const ws of wsServers) {\n const scheme = ws.server.scheme === 'https' ? 'wss' : 'ws';\n process.stdout.write(\n `Server listening on ${scheme}://${ws.server.host}:${ws.server.port}${ws.apiPath} (${ws.api.apiLogicalId} (WebSocket API))\\n`\n );\n }\n process.stdout.write('^C to stop and clean up containers.\\n');\n\n // PR 8c (extended for issue #260 to span N servers): hot reload\n // (`--watch`). For N-server topology we serialize re-synth ONCE per\n // watcher event, then per-server filter the material + swap state.\n // Adding/removing an entire API across a reload is not supported —\n // the user is warned and the server set stays static until restart.\n let watcher: FileWatcher | undefined;\n let reloadChain: Promise<unknown> = Promise.resolve();\n if (options.watch) {\n // Watch the CDK app's source tree (the synth working directory, where\n // `cdk.json` lives) so editing handler / construct source re-synths\n // and hot-reloads. We honor `cdk.json`'s `watch.include` /\n // `watch.exclude` (mirroring `cdk watch`).\n const watchRoot = process.cwd();\n const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({\n watchRoot,\n output: options.output,\n watchConfig: resolveWatchConfig(),\n });\n watcher = createFileWatcher({\n paths: [watchRoot],\n ignored,\n shouldTrigger,\n onChange: () => {\n logger.info('Detected source change; reloading...');\n const next = reloadChain.then(() =>\n reloadAllServers({\n synthesizeAndBuild,\n servers,\n buildPool,\n logger,\n })\n );\n reloadChain = next.catch(() => undefined);\n },\n });\n logger.info(\n `Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(', ')}).`\n );\n }\n\n // Graceful shutdown: SIGINT / SIGTERM / uncaughtException /\n // unhandledRejection all run the same dispose path. Double-^C\n // bypasses dispose and exits immediately so the user can escape a\n // hung Docker daemon.\n //\n // Single-flight contract (closes the SIGINT-during-SIGTERM /\n // double-signal race): the actual cleanup body is wrapped in\n // `singleFlight(...)` so a second signal that lands while the first\n // shutdown is still draining `pool.dispose()` awaits the same\n // promise instead of starting a parallel run against the shared\n // `servers` / `inlineTmpDirs` / `layerTmpDirs` cells (which would\n // otherwise double-`server.close()` and corrupt the\n // mid-iteration tmpdir set). The first signal's `signal` + `exitCode`\n // win — subsequent signals' arguments are intentionally dropped.\n // The double-^C force-exit feature is preserved by tracking the\n // started + completed state separately from the in-flight cleanup.\n let shutdownStarted = false;\n let firstSignal: string | undefined;\n let firstExitCode = 0;\n let forceExitArmed = false;\n const runCleanup = singleFlight(async (): Promise<void> => {\n logger.info(`Received ${firstSignal}, shutting down...`);\n if (watcher) {\n try {\n await watcher.close();\n } catch (err) {\n logger.warn(`watcher.close() failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n // Close every server in parallel, then dispose every (possibly hot-\n // reload-swapped) pool. Each pool's dispose() waits for in-flight\n // requests to drain; running them in parallel is the right shape\n // even for N servers because shutdown is signalled to all at once.\n //\n // WebSocket-server shutdown layers on top: each\n // `AttachedWebSocketServer.close()` sends close-frame 1001 (going\n // away) to every live socket BEFORE we kill the underlying http\n // server, so the client sees a clean close instead of a\n // mid-frame disconnect.\n await Promise.allSettled(\n wsServers.map(async (ws) => {\n try {\n await ws.attached.close();\n } catch (err) {\n logger.warn(\n `WebSocket close() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n })\n );\n await Promise.allSettled(\n servers.map(async ({ server, group }) => {\n try {\n await server.close();\n } catch (err) {\n logger.warn(\n `server.close() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n })\n );\n await Promise.allSettled(\n wsServers.map(async (ws) => {\n try {\n await ws.server.close();\n } catch (err) {\n logger.warn(\n `WebSocket server.close() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n })\n );\n await Promise.allSettled(\n servers.map(async ({ server, group }) => {\n try {\n await server.getServerState().pool.dispose();\n } catch (err) {\n logger.warn(\n `pool.dispose() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n })\n );\n await Promise.allSettled(\n wsServers.map(async (ws) => {\n try {\n await ws.server.getServerState().pool.dispose();\n } catch (err) {\n logger.warn(\n `WebSocket pool.dispose() failed for ${ws.api.apiLogicalId}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n })\n );\n // Remove every tmpdir we materialized inline `Code.ZipFile` Lambdas\n // into. Each is `mkdtempSync(...)` under the OS tmpdir, so the only\n // owner of cleanup is this process. Best-effort: log + continue on\n // any per-dir failure.\n for (const dir of inlineTmpDirs) {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch (err) {\n logger.warn(\n `Failed to remove inline-code tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n for (const dir of layerTmpDirs) {\n try {\n rmSync(dir, { recursive: true, force: true });\n } catch (err) {\n logger.warn(\n `Failed to remove merged-layers tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n // Dispose the synthesized profile credentials file (one INI file under\n // `/tmp/cdk-local-profile-creds-*/`). Leaving temp credential files on\n // disk after process exit is a security smell; `dispose()` rm's the\n // tempdir recursively + force, so re-entrant calls (signal-mid-finally\n // race) are safe.\n if (profileCredsFile) {\n try {\n await profileCredsFile.dispose();\n } catch (err) {\n logger.warn(\n `Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n }\n });\n const shutdown = async (signal: string, exitCode: number): Promise<void> => {\n if (shutdownStarted) {\n if (!forceExitArmed) {\n forceExitArmed = true;\n logger.warn(\n `Received second ${signal}; force-exiting. Orphan containers may remain — run 'docker ps --filter name=${getEmbedConfig().resourceNamePrefix}-' and 'docker rm -f' to clean up.`\n );\n process.exit(130);\n }\n return;\n }\n shutdownStarted = true;\n firstSignal = signal;\n firstExitCode = exitCode;\n await runCleanup();\n process.exit(firstExitCode);\n };\n\n process.on('SIGINT', () => {\n void shutdown('SIGINT', 130);\n });\n process.on('SIGTERM', () => {\n void shutdown('SIGTERM', 0);\n });\n process.on('uncaughtException', (err) => {\n logger.error(\n `Uncaught exception: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}`\n );\n void shutdown('uncaughtException', 1);\n });\n process.on('unhandledRejection', (reason) => {\n logger.error(\n `Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}`\n );\n void shutdown('unhandledRejection', 1);\n });\n\n // Block forever — the signal handlers exit the process.\n await new Promise<never>(() => undefined);\n}\n\n/** The `--watch` file-watcher predicates + the resolved exclude list. */\nexport interface WatchPredicates {\n /** chokidar `ignored` predicate (exclude-only; prunes dirs + files). */\n ignored: (absPath: string) => boolean;\n /** Per-event include gate — exclude wins, then include. */\n shouldTrigger: (absPath: string) => boolean;\n /** Resolved exclude glob list (mandatory + cdk.json), for logging. */\n excludePatterns: string[];\n}\n\n/**\n * Build the `--watch` file-watcher predicates for a source tree rooted\n * at `watchRoot` (the synth working directory).\n *\n * The synth output directory is always excluded so the reload's own\n * re-synth writes never re-trigger the watcher (no loop); `node_modules`\n * / `.git` are excluded for traversal cost. `cdk.json` `watch.exclude`\n * globs layer on top, and `watch.include` gates which changes fire a\n * reload. The output dir is only added as a glob when it lives UNDER the\n * watch root — when it equals the root (`''`) or sits outside it\n * (`..`-prefixed), a watcher rooted at `watchRoot` never traverses it,\n * so no exclude entry is needed (and a `''` / `..` glob is meaningless).\n *\n * `@internal` exported for unit tests (the exclude/include composition +\n * the output-dir relativization edge cases).\n */\nexport function createWatchPredicates(args: {\n watchRoot: string;\n output: string;\n watchConfig: CdkWatchConfig;\n}): WatchPredicates {\n const { watchRoot, output, watchConfig } = args;\n const toRel = (abs: string): string => path.relative(watchRoot, abs).split(path.sep).join('/');\n // `path.relative` yields `''` when the output dir IS the watch root and a\n // `..`-prefixed path when it is outside — in both cases a watcher rooted\n // at `watchRoot` never traverses it, so it needs no exclude glob.\n const outputRel = toRel(path.resolve(watchRoot, output));\n const mandatoryExcludes = outputRel !== '' && !outputRel.startsWith('..') ? [outputRel] : [];\n const excludePatterns = [...mandatoryExcludes, 'node_modules', '.git', ...watchConfig.exclude];\n const excludeMatcher = createGlobMatcher(excludePatterns);\n const includeMatcher = createGlobMatcher(watchConfig.include);\n const ignored = (absPath: string): boolean => {\n const rel = toRel(absPath);\n if (rel === '') return false; // the watch root itself\n if (rel.startsWith('..')) return true; // outside the root\n return excludeMatcher(rel);\n };\n const shouldTrigger = (absPath: string): boolean => {\n const rel = toRel(absPath);\n if (rel === '' || rel.startsWith('..')) return false;\n if (excludeMatcher(rel)) return false;\n return includeMatcher(rel);\n };\n return { ignored, shouldTrigger, excludePatterns };\n}\n\n/**\n * Match the `--stack` pattern (or single-stack auto-detect) to a list\n * of stacks the route-discovery walks. Mirrors the deploy/diff matcher\n * routing rules.\n */\n/** @internal exported for unit tests. */\nexport function pickTargetStacks(\n stacks: StackInfo[],\n pattern: string | undefined,\n cfnStackFallback?: string,\n targetFallback?: string,\n allStacks?: boolean\n): StackInfo[] {\n // Issue #55: `--all-stacks` serves the union of every stack's APIs.\n // It is mutually exclusive with the selectors below (validated by the\n // caller), so when set we skip the resolution chain entirely and\n // return every stack. The downstream server grouping disambiguates\n // same-logical-id collisions across stacks, so a union boots fine.\n if (allStacks) return stacks;\n\n // Resolution chain (first non-empty wins):\n // 1. `--stack <pattern>` (explicit)\n // 2. `--from-cfn-stack <explicit-name>` (PR #44)\n // 3. positional target's stack-name prefix (PR #45)\n // e.g. `cdkl start-api MyStack/MyApi` infers `MyStack`.\n //\n // CDK apps typically deploy each synth stack under its own stack-name,\n // so any of the three values identifies the synth target unambiguously\n // without the user having to repeat themselves via `--stack`.\n const effective = pattern ?? cfnStackFallback ?? targetFallback;\n if (effective) {\n return matchStacks(stacks, [effective]);\n }\n if (stacks.length === 1) return stacks;\n if (stacks.length === 0) return [];\n // Multi-stack apps are served as a union only on explicit opt-in\n // (`--all-stacks`); otherwise we require an explicit single-target\n // selection so users don't accidentally serve (and boot Docker\n // containers for) a side-stack's API.\n throw new Error(\n `Multi-stack app: pass --stack <name>, --from-cfn-stack <name>, a stack-qualified target like \"<StackName>/<construct>\", or --all-stacks to serve every stack. Available stacks: ${stacks.map((s) => s.stackName).join(', ')}.`\n );\n}\n\n/**\n * Issue #55: `--all-stacks` serves every stack's API as a union, so it\n * cannot be combined with a selector that names a target subset.\n * Returns the human-readable list of conflicting selectors (empty when\n * `--all-stacks` is off or there is no conflict).\n *\n * The bare `--from-cfn-stack` flag (Commander maps it to boolean `true`)\n * is NOT a conflict — it binds each routed stack to its own CFn stack,\n * which is exactly the multi-stack union case. Only an explicit\n * `--from-cfn-stack <name>` (a string) names a single stack and conflicts.\n *\n * Extracted as a pure function so it can be unit-tested without booting\n * the full server.\n *\n * @internal exported for unit tests.\n */\nexport function allStacksConflicts(\n options: Pick<LocalStartApiOptions, 'allStacks' | 'stack' | 'api' | 'fromCfnStack'>,\n targets: readonly string[],\n apiFilters: readonly string[]\n): string[] {\n if (!options.allStacks) return [];\n const conflicts: string[] = [];\n if (apiFilters.length > 0) {\n conflicts.push(\n targets.length > 0 ? `target(s) [${targets.join(', ')}]` : `--api '${options.api}'`\n );\n }\n if (options.stack !== undefined) conflicts.push(`--stack '${options.stack}'`);\n if (typeof options.fromCfnStack === 'string') {\n conflicts.push(`--from-cfn-stack '${options.fromCfnStack}'`);\n }\n return conflicts;\n}\n\n/**\n * Result of {@link resolveApiTargetSubset}: the filtered route union plus\n * the list of identifiers that matched nothing (so the caller can warn\n * once per typo without re-running the per-id filter).\n */\nexport interface ApiTargetSubset {\n /** The UNION of routes matching any supplied identifier. Non-empty. */\n readonly filtered: RouteWithAuth[];\n /** Identifiers that matched zero routes — order preserved from input. */\n readonly unmatched: string[];\n}\n\n/**\n * Resolve the variadic `cdkl start-api <target...>` subset against the\n * discovered route surface — the pure core of the subset-serving path.\n *\n * Behavior:\n * - Rejects a BARE logical id (no `:`, no `/`) when the app has more than\n * one stack: a bare id is ambiguous because two stacks can carry the\n * same logical id (mirrors `cdkl invoke` / `cdkl run-task`'s resolver).\n * THROWS with the disambiguation hint.\n * - Filters routes to the UNION of the identifiers via\n * {@link filterRoutesByApiIdentifiers}. THROWS when the union is empty\n * (no identifier matched anything), listing the available identifiers.\n * - Returns the surviving union in `filtered` and the identifiers that\n * matched nothing in `unmatched`. A single typo among valid ids keeps\n * the siblings in `filtered` and surfaces the typo via `unmatched`;\n * the caller is responsible for warning (so the warning can be gated\n * one-shot across `--watch` reloads — see the call site).\n *\n * `availableApiIdentifiers(routes)` is computed ONCE here (not per-id) so\n * the resolution is O(N·M) rather than O(N²·M).\n *\n * @internal exported for unit tests + library consumers (re-exported from\n * the package entry).\n */\nexport function resolveApiTargetSubset(\n routes: readonly RouteWithAuth[],\n identifiers: readonly string[],\n stackNames: readonly string[]\n): ApiTargetSubset {\n // Strict multi-stack rejection for the bare-logical-id form (no `:`,\n // no `/`). A bare id in a multi-stack app is ambiguous, because two\n // stacks can legitimately have the same logical id — pre-PR the filter\n // would silently match both and collide in `groupRoutesByServer`'s\n // serverKey. We reject upfront with the same disambiguation hint\n // `cdkl invoke` uses.\n if (stackNames.length > 1) {\n for (const id of identifiers) {\n const isBareId = !id.includes(':') && !id.includes('/');\n if (isBareId) {\n throw new Error(\n `Multiple stacks in app, target '${id}' is missing a stack prefix. ` +\n `Use 'StackName:${id}' or 'StackName/${id}' (Construct path form). ` +\n `Available stacks: ${stackNames.join(', ')}.`\n );\n }\n }\n }\n\n const filtered = filterRoutesByApiIdentifiers(routes, identifiers);\n if (filtered.length === 0) {\n const available = availableApiIdentifiers(routes).join(', ') || '(none)';\n throw new Error(\n `Target(s) [${identifiers.join(', ')}] did not match any discovered API. Available identifiers: ${available}.`\n );\n }\n\n // Surface any individual identifier that matched nothing (the union\n // still has routes from its siblings, so the run continues) — a single\n // typo in a multi-target list otherwise silently serves a smaller set\n // than the user asked for. `availableApiIdentifiers(routes)` is hoisted\n // out of this loop (computed once below) for the caller's warning.\n const unmatched = identifiers.filter(\n (id) => filterRoutesByApiIdentifiers(routes, [id]).length === 0\n );\n\n return { filtered, unmatched };\n}\n\n/**\n * Derive the single synth stack prefix shared by every supplied target,\n * for the synth-scope optimization (item 5 of the start-api subset UX).\n *\n * Each target's stack prefix is the segment before its first `/` (the\n * `<StackName>/<construct>` Construct-path form). Returns:\n * - `undefined` when `targets` is empty (serve-all path — no prefix).\n * - `undefined` when ANY target lacks a `/` (bare logical id — the synth\n * target can't be inferred from it, so fall back to the existing\n * single-stack auto-pick / multi-stack rejection path).\n * - `undefined` when targets span MORE THAN ONE distinct prefix (the\n * subset crosses stacks, so synth must cover all of them — see\n * {@link shouldSynthAllStacks}).\n * - the shared prefix when every target carries the SAME `<StackName>/`\n * prefix — keeping the single-stack synth optimization.\n *\n * @internal exported for unit tests.\n */\nexport function deriveSynthStackPrefix(targets: readonly string[]): string | undefined {\n if (targets.length === 0) return undefined;\n const prefixes = new Set<string>();\n for (const t of targets) {\n const slash = t.indexOf('/');\n if (slash === -1) return undefined; // bare logical id — can't infer a stack\n prefixes.add(t.slice(0, slash));\n }\n return prefixes.size === 1 ? [...prefixes][0] : undefined;\n}\n\n/**\n * Decide whether synth should cover ALL stacks for the given target\n * subset (item 5). Synth is scoped to one stack only when a single stack\n * can be pinned; otherwise we synth every stack and the union is filtered\n * down by `apiFilters` afterwards.\n *\n * Returns `true` (synth all) when there ARE explicit targets but no\n * single stack can be pinned from them — i.e. `--stack` is unset, an\n * explicit `--from-cfn-stack <name>` is unset, AND\n * {@link deriveSynthStackPrefix} can't resolve one shared prefix (targets\n * span stacks, or any target is a bare logical id). Returns `false` when\n * there are no targets (serve-all already synths everything via the\n * regular path) or a stack is otherwise pinned.\n *\n * @internal exported for unit tests.\n */\nexport function shouldSynthAllStacks(\n targets: readonly string[],\n stackPattern: string | undefined,\n cfnStackFallback: string | undefined\n): boolean {\n if (targets.length === 0) return false;\n if (stackPattern !== undefined || cfnStackFallback !== undefined) return false;\n return deriveSynthStackPrefix(targets) === undefined;\n}\n\n/**\n * Decide whether bare `start-api` should open the pre-selected-all\n * multi-select picker. The picker runs ONLY when:\n * - no explicit API subset was named (`apiFilters` empty — neither\n * positional `<targets...>` nor the deprecated `--api`),\n * - `--all-stacks` was not passed (that path already serves every API),\n * - AND stdin/stdout is a TTY.\n *\n * In a non-TTY (CI / pipe) this returns `false`, and the caller serves\n * EVERY discovered API without prompting — start-api's one intentional\n * asymmetry vs invoke / run-task (which error when bare in a non-TTY),\n * because start-api legitimately has a \"serve all\" default.\n *\n * Extracted as a pure function (TTY is passed in) so the decision is\n * unit-testable without booting the server or juggling global TTY state.\n *\n * @internal exported for unit tests.\n */\nexport function shouldPromptBareMultiSelect(\n apiFilters: readonly string[],\n allStacks: boolean | undefined,\n isTty: boolean\n): boolean {\n return apiFilters.length === 0 && !allStacks && isTty;\n}\n\n/**\n * Decide whether the `--from-cfn-stack <name>` redundancy tip should\n * fire for the current invocation. Fires only when:\n * - `fromCfnStack` is a non-empty STRING (explicit value, not bare `true`)\n * - exactly ONE stack is routed\n * - the explicit value equals the routed stack's `stackName`\n *\n * Extracted as a pure function so it can be unit-tested without booting\n * the full server. See improvement A in the start-api UX PR.\n *\n * @internal exported for unit tests.\n */\nexport function shouldEmitFromCfnRedundancyTip(\n fromCfnStack: string | boolean | undefined,\n routedStackNames: readonly string[]\n): boolean {\n if (typeof fromCfnStack !== 'string') return false;\n if (fromCfnStack.length === 0) return false;\n if (routedStackNames.length !== 1) return false;\n return fromCfnStack === routedStackNames[0];\n}\n\n/**\n * One-shot wrapper around `shouldEmitFromCfnRedundancyTip` for the\n * `--watch` hot-reload path. `synthesizeAndBuild` re-runs on every\n * reload firing, so without a gate the tip would re-emit on every\n * reload — noisy. This helper consults the caller-supplied ref:\n * - If the predicate fires AND the ref is still `false`, calls `emit`\n * and flips the ref to `true`.\n * - On subsequent invocations the ref is `true` and the helper is a\n * no-op for the rest of the ref's lifetime.\n * - When the predicate does NOT fire (no `--from-cfn-stack` value /\n * intentionally-different value / multi-stack run), the ref stays\n * `false` so a future reload whose synthesized stacks change in a\n * way that DOES make the value redundant still emits the tip once.\n *\n * The ref is owned by `localStartApiCommand` (one per server boot), so\n * independent server invocations get independent flags.\n *\n * @internal exported for unit tests.\n */\nexport function tryEmitFromCfnRedundancyTipOnce(\n fromCfnStack: string | boolean | undefined,\n routedStackNames: readonly string[],\n emittedRef: { value: boolean },\n emit: (routedStackName: string) => void\n): void {\n if (emittedRef.value) return;\n if (!shouldEmitFromCfnRedundancyTip(fromCfnStack, routedStackNames)) return;\n const routedStackName = routedStackNames[0];\n if (routedStackName === undefined) return;\n emit(routedStackName);\n emittedRef.value = true;\n}\n\n/**\n * Distinct, stable list of Lambda logical IDs reachable through any\n * discovered route OR referenced by a Lambda authorizer attached to one\n * of those routes. Stable order = first-occurrence order in the routes\n * list, then any newly-introduced authorizer Lambdas, which keeps the\n * route-table output deterministic.\n */\nfunction uniqueLambdaIds(\n routes: readonly DiscoveredRoute[],\n routesWithAuth: readonly RouteWithAuth[],\n webSocketApis: readonly DiscoveredWebSocketApi[] = []\n): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const r of routes) {\n // Skip deferred-error unsupported routes, synthetic mockCors\n // routes, and service-integration routes — none of them dispatch\n // to a Lambda, so spinning up their containers (when a\n // lambdaLogicalId IS attached, e.g. on a Function URL with\n // AuthType=AWS_IAM) would be wasted boot time.\n if (r.unsupported || r.mockCors || r.serviceIntegration) continue;\n if (r.lambdaLogicalId.length === 0) continue;\n if (!seen.has(r.lambdaLogicalId)) {\n seen.add(r.lambdaLogicalId);\n out.push(r.lambdaLogicalId);\n }\n }\n for (const entry of routesWithAuth) {\n // An unsupported / mockCors / service-integration route never\n // reaches the authorizer pass, so its authorizer Lambda doesn't\n // need a container either.\n if (entry.route.unsupported || entry.route.mockCors || entry.route.serviceIntegration) {\n continue;\n }\n const auth = entry.authorizer;\n if (!auth) continue;\n if (auth.kind === 'lambda-token' || auth.kind === 'lambda-request') {\n if (!seen.has(auth.lambdaLogicalId)) {\n seen.add(auth.lambdaLogicalId);\n out.push(auth.lambdaLogicalId);\n }\n }\n }\n // #462: pull every WebSocket route's backing Lambda into the\n // unified spec map so the shared container pool can dispatch them\n // alongside HTTP / REST routes.\n for (const api of webSocketApis) {\n for (const r of api.routes) {\n if (!seen.has(r.targetLambdaLogicalId)) {\n seen.add(r.targetLambdaLogicalId);\n out.push(r.targetLambdaLogicalId);\n }\n }\n }\n return out;\n}\n\n/**\n * Prefetch the JWKS for every Cognito / JWT authorizer attached to a\n * discovered route. Failures degrade to pass-through mode (verifier\n * surfaces a warn line on first hit); we still issue the prefetch so\n * the warn lands at startup rather than mid-request.\n */\nasync function prewarmJwks(\n routesWithAuth: readonly RouteWithAuth[],\n jwksCache: import('../../local/cognito-jwt.js').JwksCache\n): Promise<void> {\n const urls = new Set<string>();\n for (const entry of routesWithAuth) {\n const auth = entry.authorizer;\n if (!auth) continue;\n if (auth.kind === 'cognito') {\n // Multi-pool federation: pre-warm JWKS for EVERY pool in\n // ProviderARNs[], so issuer-matched verification at request time\n // doesn't pay a cold-start latency on the first request to each\n // tenant's pool.\n for (const pool of auth.pools) {\n urls.add(buildCognitoJwksUrl(pool.region, pool.userPoolId));\n }\n } else if (auth.kind === 'jwt') {\n const url =\n auth.region && auth.userPoolId\n ? buildCognitoJwksUrl(auth.region, auth.userPoolId)\n : buildJwksUrlFromIssuer(auth.issuer);\n urls.add(url);\n }\n }\n await Promise.all([...urls].map((u) => jwksCache.fetchAndCache(u)));\n}\n\n/**\n * Emit a one-line warn for every VPC-config Lambda. The handler still\n * runs locally, but its container does not get attached to the AWS\n * VPC's subnets — calls to private RDS / ElastiCache will fail. cdk-local\n * surfaces this so the developer can pin the unexpected behavior to\n * the VPC config rather than chasing a \"connection refused\" rabbit\n * hole.\n */\nfunction warnVpcConfigLambdas(\n routesWithAuth: readonly RouteWithAuth[],\n stacks: readonly StackInfo[]\n): void {\n const logger = getLogger();\n // Walk every reachable Lambda (route handler + authorizer) once.\n const seen = new Set<string>();\n const reachable: string[] = [];\n for (const entry of routesWithAuth) {\n if (!seen.has(entry.route.lambdaLogicalId)) {\n seen.add(entry.route.lambdaLogicalId);\n reachable.push(entry.route.lambdaLogicalId);\n }\n const auth: AuthorizerInfo | undefined = entry.authorizer;\n if (auth && (auth.kind === 'lambda-token' || auth.kind === 'lambda-request')) {\n if (!seen.has(auth.lambdaLogicalId)) {\n seen.add(auth.lambdaLogicalId);\n reachable.push(auth.lambdaLogicalId);\n }\n }\n }\n for (const logicalId of reachable) {\n for (const stack of stacks) {\n const resource = stack.template.Resources?.[logicalId];\n if (!resource || resource.Type !== 'AWS::Lambda::Function') continue;\n const props = resource.Properties ?? {};\n const vpcConfig = props['VpcConfig'];\n if (vpcConfig && typeof vpcConfig === 'object' && Object.keys(vpcConfig).length > 0) {\n logger.warn(\n `Lambda ${logicalId} has VpcConfig — local container will reach external services via the host's network, NOT through the deployed VPC's NAT/private subnets. Calls to private RDS/ElastiCache will fail.`\n );\n }\n break;\n }\n }\n}\n\n/**\n * Walk the discovered routes for `AuthorizationType: 'AWS_IAM'` and emit\n * a one-line warn naming the affected routes. Returns `true` when at\n * least one IAM route is present so the caller wires the SigV4\n * credentials loader. Re-runs across hot reloads are silent — the warn\n * fires only at initial boot (matches `warnVpcConfigLambdas`'s policy).\n *\n * Implementation note: signature verification only — IAM policy\n * evaluation (resource / action / condition) is NOT emulated. See\n * `src/local/sigv4-verify.ts` and the help text in `docs/cli-reference.md`.\n */\nfunction warnIamRoutes(routesWithAuth: readonly RouteWithAuth[]): boolean {\n const logger = getLogger();\n const iamRoutes: string[] = [];\n const oacRoutes: string[] = [];\n for (const entry of routesWithAuth) {\n if (entry.authorizer?.kind !== 'iam') continue;\n // OAC-fronted Function URLs are reported separately: their SigV4 check\n // is auto-relaxed (CloudFront signs origin requests in production), so\n // the generic \"verifies signatures against your local credentials\" line\n // would mislead.\n if (entry.authorizer.oacFronted === true) {\n oacRoutes.push(entry.route.declaredAt);\n } else {\n iamRoutes.push(entry.route.declaredAt);\n }\n }\n if (iamRoutes.length === 0 && oacRoutes.length === 0) return false;\n if (iamRoutes.length > 0) {\n logger.warn(\n `${iamRoutes.length} route(s) declare AuthorizationType: AWS_IAM — ${getEmbedConfig().cliName} start-api ` +\n `verifies the SigV4 signatures it CAN (requests signed with your local AWS credentials), but ` +\n `cannot verify a federated / Cognito Identity Pool / cross-account signer and does NOT emulate ` +\n `IAM policy evaluation (resource / action / condition rules). By default such unverifiable ` +\n `requests warn-and-pass with a placeholder principalId — pass --strict-sigv4 to deny them ` +\n `instead. Downstream authorization is the dev's responsibility.`\n );\n for (const declaredAt of iamRoutes) {\n logger.warn(` - ${declaredAt}`);\n }\n }\n if (oacRoutes.length > 0) {\n logger.warn(\n `${oacRoutes.length} Function URL route(s) with AuthType: AWS_IAM are fronted by a CloudFront ` +\n `Origin Access Control. In production CloudFront re-signs the origin request, so no local ` +\n `client signature can be verified — ${getEmbedConfig().cliName} start-api passes these through (warn-and-pass) ` +\n `and they ignore --strict-sigv4. Do NOT trust the request identity in handler code.`\n );\n for (const declaredAt of oacRoutes) {\n logger.warn(` - ${declaredAt}`);\n }\n }\n return true;\n}\n\n/**\n * Build the per-Lambda container spec — code dir, env vars (template +\n * --env-vars overlay), STS-issued creds when --assume-role names this\n * Lambda, optional --debug-port reservation. Errors out with a clear\n * message if the Lambda's code can't be resolved (asset directory\n * missing, runtime not supported).\n */\nasync function buildContainerSpec(args: {\n logicalId: string;\n stacks: StackInfo[];\n overrides: EnvOverrideFile | undefined;\n assumeRole: AssumeRoleOption | undefined;\n containerHost: string;\n debugPort?: number;\n stsRegion: string | undefined;\n /**\n * The caller's set of materialized inline-code tmpdirs. Every dir\n * `materializeInlineCode` returns is also pushed here so the graceful\n * shutdown path can remove it. The set is shared across all calls\n * within one server boot.\n */\n inlineTmpDirs: Set<string>;\n /**\n * The caller's set of merged-layers tmpdirs (PR 6 of #224, issue\n * #232). Every multi-layer Lambda's `materializeLambdaLayers(...)`\n * call records its merged tmpdir here so `shutdown(...)` can remove\n * each one. Single-layer Lambdas bind-mount the layer's asset dir\n * directly and never write into this set.\n */\n layerTmpDirs: Set<string>;\n /**\n * `--from-state` substitution input keyed by stack name. Empty when\n * the flag is unset OR when no routed stack had loadable state. Per-\n * stack entries supply state.resources + the pseudo-parameter bag\n * the env-resolver consults for `Ref AWS::*` / `${AWS::*}` placeholders.\n */\n stateByStack: Map<string, StackStateBundle>;\n /**\n * `--no-pull` flag, threaded to the IMAGE branch's ECR-pull fallback\n * (`pullEcrImage(... {skipPull})`). ZIP branch ignores this — the\n * base-image pull happens once up the call chain\n * (`synthesizeAndBuild`'s `pullImage` loop) and is independently\n * gated by the same flag.\n */\n skipPull: boolean;\n /**\n * The CLI's `--profile`, forwarded to the IMAGE branch's ECR-pull\n * fallback so `ecr:GetAuthorizationToken` authenticates as the\n * profile's account (matching the ECS path). Undefined when\n * `--profile` is unset.\n */\n profile?: string;\n /**\n * Issue #448: optional `--layer-role-arn` value. Forwarded into\n * {@link materializeLambdaLayers}, which `sts:AssumeRole`s into this\n * role before calling `lambda:GetLayerVersion` for every literal-ARN\n * entry. Same role applies to every routed Lambda — if the user's\n * apps reference layers in multiple accounts they need a single\n * cross-account role that can read all of them.\n */\n layerRoleArn?: string;\n /**\n * Issue #654: profile-resolved credentials to forward into the Lambda\n * container's env when `--profile <p>` is set AND this Lambda has no\n * `--assume-role` mapping. Resolved once at server boot via\n * `resolveProfileCredentials` and shared across every spec build.\n * Undefined when `--profile` is unset (the pre-fix path stays in\n * effect — `forwardAwsEnv` copies `process.env.AWS_*`).\n */\n profileCredentials?: { accessKeyId: string; secretAccessKey: string; sessionToken?: string };\n /**\n * When set, a synthesized AWS shared credentials file (one INI section,\n * the resolved\n * `[<profile-name>]` block) the container should bind-mount + reference\n * via `AWS_SHARED_CREDENTIALS_FILE` + `AWS_PROFILE` env vars. Lets\n * handlers that use `fromIni({ profile })` explicitly inside their\n * code resolve to the same creds locally. Undefined when `--profile`\n * was not passed.\n */\n profileCredsFile?: ProfileCredentialsFile;\n /**\n * `--profile`'s configured region (`~/.aws/config`'s `region =` for the\n * profile), resolved once at server boot via\n * {@link resolveProfileCredentials}. Lowest-precedence fallback for the\n * container's `AWS_REGION` — used only when no explicit region was\n * injected. Undefined when `--profile` is unset or the profile has no\n * region.\n */\n profileRegion?: string | undefined;\n /**\n * `--stack-region` flag value. Highest-precedence fallback for the\n * container's `AWS_REGION` (it also drives the `--from-cfn-stack` CFn\n * client, so the container reaches the same region as the resources it\n * is bound to). Undefined when the flag is unset.\n */\n stackRegionOverride?: string | undefined;\n}): Promise<ContainerSpec> {\n const {\n logicalId,\n stacks,\n overrides,\n assumeRole,\n containerHost,\n debugPort,\n stsRegion,\n inlineTmpDirs,\n layerTmpDirs,\n stateByStack,\n skipPull,\n layerRoleArn,\n profileCredentials,\n profileCredsFile,\n profileRegion,\n stackRegionOverride,\n } = args;\n const lambda = resolveLambdaByLogicalId(logicalId, stacks);\n\n // ZIP / IMAGE divergence (closes #453). ZIP needs `codeDir` (bind-\n // mount source for /var/task) + `optDir` (bind-mount source for\n // /opt from same-stack Layers). IMAGE needs a pre-built local\n // docker image tag — resolved here once at server boot, then the\n // container pool runs `docker run` against it without further\n // resolution. Layers are silently ignored on the IMAGE branch\n // (matches AWS's invoke-time behavior: container Lambdas can't\n // declare Layers).\n let codeDir: string | undefined;\n let optDir: string | undefined;\n let imageRef: string | undefined;\n let platform: string | undefined;\n if (lambda.kind === 'zip') {\n // Re-use `cdkl invoke`'s materialization rules for inline\n // (Code.ZipFile) Lambdas; asset-backed Lambdas already point at an\n // unzipped CDK directory.\n codeDir =\n lambda.codePath ??\n materializeInlineCode(\n lambda.handler,\n lambda.inlineCode ?? '',\n resolveRuntimeFileExtension(lambda.runtime),\n inlineTmpDirs\n );\n\n // PR 6 (#232): pre-resolve the `/opt` bind-mount source. Single-\n // layer functions reuse the layer's asset dir directly; multi-\n // layer functions get a freshly-merged tmpdir (later layers\n // overwrite earlier files via `cpSync({force:true})` — the\n // load-bearing half of AWS's \"last layer wins\" semantic).\n //\n // Issue #448: literal-ARN entries are downloaded + unzipped via\n // `lambda:GetLayerVersion` before the cpSync-merge step. The per-ARN\n // tmpdirs are tracked in `layerTmpDirs` alongside multi-layer merge\n // dirs so the same shutdown path cleans every one.\n optDir = await materializeLambdaLayers(lambda.layers, layerTmpDirs, layerRoleArn);\n } else {\n // IMAGE branch (closes #453): build locally from cdk.out asset\n // manifest, OR pull from ECR when no matching asset is found.\n // Same-account/region only on the ECR-pull path — cross-account /\n // cross-region is deferred to a sibling PR (W2-1).\n const built = await resolveContainerImageForStartApi(lambda, skipPull, args.profile);\n imageRef = built.imageRef;\n platform = architectureToPlatform(lambda.architecture);\n }\n\n // Env vars: literal template values + --env-vars overlay. When\n // `--from-state` was passed (and state for this Lambda's stack\n // loaded), intrinsic-valued template entries are first substituted\n // against deployed-stack state + AWS pseudo parameters via\n // `substituteEnvVarsFromState`. Per-key failures (state missing for\n // a referenced logical id, attribute not captured at deploy time,\n // unsupported intrinsic shape) warn-and-drop with context. Keys the\n // state-resolver already warned about are suppressed in the downstream\n // env-resolver's generic warn loop so the user sees one warn per key.\n let templateEnv = getTemplateEnv(lambda.resource);\n const stateBundle = stateByStack.get(lambda.stack.stackName);\n let stateAudit: ReturnType<typeof substituteEnvVarsFromState>['audit'] | undefined;\n if (stateBundle) {\n const context: SubstitutionContext = { resources: stateBundle.state.resources };\n if (stateBundle.pseudoParameters) {\n context.pseudoParameters = stateBundle.pseudoParameters;\n }\n if (stateBundle.ssmParameters) {\n context.parameters = stateBundle.ssmParameters;\n }\n // Flag decrypted SecureString parameters so the consuming env keys are\n // kept off the `docker run` argv (issue #99).\n if (stateBundle.ssmSecureStringLogicalIds?.length) {\n context.sensitiveParameters = new Set(stateBundle.ssmSecureStringLogicalIds);\n }\n const { env, audit } = substituteEnvVarsFromState(templateEnv, context);\n templateEnv = env;\n for (const key of audit.resolvedKeys) {\n getLogger().debug(`Lambda ${logicalId}: state source substituted env var ${key}`);\n }\n // Deployed-env fallback: fill keys static substitution dropped (e.g.\n // `Fn::GetAtt <Sibling>.Arn`) from the consumer function's\n // deploy-time-resolved env, fetched during the state-load pass (see\n // `loadStateForRoutedStacks`). Only populated under `--from-cfn-stack`.\n let unresolved = audit.unresolved;\n const resolvedKeys = [...audit.resolvedKeys];\n const deployedEnv = stateBundle.deployedEnvByLambda?.get(logicalId);\n if (unresolved.length > 0 && deployedEnv) {\n const fb = applyDeployedEnvFallback(templateEnv, unresolved, deployedEnv);\n templateEnv = fb.env;\n unresolved = fb.stillUnresolved;\n for (const key of fb.filled) {\n resolvedKeys.push(key);\n getLogger().debug(\n `Lambda ${logicalId}: filled env var ${key} from deployed function config`\n );\n }\n }\n stateAudit = { resolvedKeys, unresolved, sensitiveKeys: audit.sensitiveKeys };\n for (const { key, reason } of unresolved) {\n getLogger().warn(\n `Lambda ${logicalId}: state source could not substitute env var ${key} (${reason}). ` +\n `Override it via --env-vars or it will be dropped.`\n );\n }\n }\n const lambdaCdkPath = readCdkPathOrUndefined(lambda.resource);\n const envResult = resolveEnvVars(logicalId, lambdaCdkPath, templateEnv, overrides);\n for (const key of envResult.unresolved) {\n // The state-resolver already warned for keys it tried + failed on\n // (defensive: substituteEnvVarsFromState drops unresolved keys from\n // the returned env so the env-resolver never sees them, but mirror\n // `cdkl invoke --from-state`'s safety dedupe in case the\n // state-resolver evolves).\n if (stateAudit && stateAudit.unresolved.some((u) => u.key === key)) continue;\n // Prefer the L2 form (`MyStack/MyFn`) in the suggestion since that\n // matches the README guidance and `cdkl invoke` target shape; the\n // resolver's prefix rule accepts either form.\n const overrideKeyExample = lambdaCdkPath?.replace(/\\/Resource$/, '') ?? logicalId;\n getLogger().warn(\n `Lambda ${logicalId}: env var ${key} contains a CloudFormation intrinsic and was dropped. ` +\n `Override it with --env-vars (e.g. {\"${overrideKeyExample}\":{\"${key}\":\"<literal>\"}}) ` +\n `or pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to recover deployed values.`\n );\n }\n\n const dockerEnv: Record<string, string> = {\n AWS_LAMBDA_FUNCTION_NAME: logicalId,\n AWS_LAMBDA_FUNCTION_MEMORY_SIZE: String(lambda.memoryMb),\n AWS_LAMBDA_FUNCTION_TIMEOUT: String(lambda.timeoutSec),\n AWS_LAMBDA_FUNCTION_VERSION: '$LATEST',\n AWS_LAMBDA_LOG_GROUP_NAME: `/aws/lambda/${logicalId}`,\n AWS_LAMBDA_LOG_STREAM_NAME: 'local',\n ...envResult.resolved,\n };\n\n const roleArn = effectiveAssumeRoleArn(logicalId, assumeRole);\n if (roleArn) {\n const creds = await assumeLambdaExecutionRole(roleArn, stsRegion);\n dockerEnv['AWS_ACCESS_KEY_ID'] = creds.accessKeyId;\n dockerEnv['AWS_SECRET_ACCESS_KEY'] = creds.secretAccessKey;\n dockerEnv['AWS_SESSION_TOKEN'] = creds.sessionToken;\n if (stsRegion) dockerEnv['AWS_REGION'] = stsRegion;\n } else {\n forwardAwsEnv(dockerEnv);\n // Issue #654: when `--profile <p>` is set AND this Lambda has no\n // `--assume-role` mapping, overlay the profile-resolved credentials\n // on top of whatever `forwardAwsEnv` copied from `process.env`. The\n // overlay covers SSO / IAM Identity Center / regular access key /\n // role-assumption profiles uniformly (resolved via the SDK's\n // default credential chain). Without this overlay, a dev who runs\n // `cdkl start-api --profile dev` AND has no\n // `AWS_ACCESS_KEY_ID` env var (the common SSO / Identity Center\n // case) sees the Lambda boot with no creds → handler's AWS SDK\n // call fails with `Could not load credentials from any providers`.\n //\n // Region from forwardAwsEnv is preserved — only the credential\n // triple is overlaid. Existing precedence: assume-role > profile >\n // forwarded process env.\n if (profileCredentials) {\n dockerEnv['AWS_ACCESS_KEY_ID'] = profileCredentials.accessKeyId;\n dockerEnv['AWS_SECRET_ACCESS_KEY'] = profileCredentials.secretAccessKey;\n if (profileCredentials.sessionToken) {\n dockerEnv['AWS_SESSION_TOKEN'] = profileCredentials.sessionToken;\n } else {\n // The profile resolved to long-lived creds (no session token).\n // Strip any inherited session token to avoid the SDK trying\n // to use a mismatched (long-lived AKID + foreign session).\n delete dockerEnv['AWS_SESSION_TOKEN'];\n }\n }\n\n // Profile-aware SDK config inside container: when the server boot\n // synthesized a credentials file via\n // `writeProfileCredentialsFile(options.profile, ...)`, point the\n // container's SDK chain at the bind-mounted path so\n // `fromIni({ profile: '<name>' })` calls inside the handler resolve\n // to the same creds as the default chain. `AWS_PROFILE` makes\n // `fromIni()` (no explicit arg) ALSO use this profile — both code\n // paths converge on the same `[<name>]` section.\n //\n // GATED on `!roleArn` (nested in the assume-role-NOT-effective branch)\n // so the existing precedence \"assume-role > profile > env\" holds for\n // the file path too. Without this nesting, a handler in an\n // assume-role'd Lambda that called `fromIni({ profile: '<name>' })`\n // explicitly would silently bypass the execution-role creds and use\n // the `--profile <name>` creds — breaking the documented precedence.\n if (profileCredsFile) {\n dockerEnv['AWS_SHARED_CREDENTIALS_FILE'] = profileCredsFile.containerPath;\n dockerEnv['AWS_PROFILE'] = profileCredsFile.profileName;\n }\n }\n\n // Fallback AWS_REGION for the container's AWS SDK. The branches above\n // only set AWS_REGION from the assume-role STS region or a forwarded\n // AWS_REGION / AWS_DEFAULT_REGION env var. `--profile` injects the\n // credential triple but NOT the profile's region (the synthesized\n // credentials file carries no `region =`), and the synth-derived stack\n // region was previously used only host-side for the --from-cfn-stack CFn\n // client. Seed both here (precedence in resolveContainerFallbackRegion)\n // so a handler's ambient-region SDK call resolves to the same region the\n // deployed function runs in instead of failing with \"Region is missing\".\n if (!dockerEnv['AWS_REGION']) {\n const fallbackRegion = resolveContainerFallbackRegion({\n stackRegionOverride,\n synthRegion: lambda.stack.region,\n profileRegion,\n });\n if (fallbackRegion) dockerEnv['AWS_REGION'] = fallbackRegion;\n }\n\n if (debugPort !== undefined) {\n dockerEnv['NODE_OPTIONS'] = `--inspect-brk=0.0.0.0:${debugPort}`;\n }\n\n // Issue #440 — Lambda EphemeralStorage.Size: when the function's\n // template declared `EphemeralStorage`, plumb the configured `/tmp`\n // cap through to every cold-start of this Lambda's warm pool so\n // the local container's `/tmp` matches the deployed function's\n // sized tmpfs. Resolved here (once at server boot) rather than at\n // acquire-time because the value is template-static for the\n // server's lifetime. Applies to BOTH ZIP and IMAGE Lambdas.\n const tmpfs =\n lambda.ephemeralStorageMb !== undefined\n ? { target: '/tmp', sizeMb: lambda.ephemeralStorageMb }\n : undefined;\n\n // Env keys whose value resolved to a decrypted SecureString SSM parameter\n // (issue #99) — routed off the `docker run` argv via the pool's\n // value-from-process-env form.\n const sensitiveEnvKeys =\n stateAudit && stateAudit.sensitiveKeys.length > 0\n ? new Set(stateAudit.sensitiveKeys)\n : undefined;\n\n if (lambda.kind === 'zip') {\n const spec: ContainerSpec = {\n kind: 'zip',\n lambda,\n codeDir: codeDir!,\n env: dockerEnv,\n ...(sensitiveEnvKeys && { sensitiveEnvKeys }),\n containerHost,\n ...(optDir !== undefined && { optDir }),\n ...(debugPort !== undefined && { debugPort }),\n ...(tmpfs !== undefined && { tmpfs }),\n ...(profileCredsFile && {\n profileCredentialsFile: {\n hostPath: profileCredsFile.hostPath,\n containerPath: profileCredsFile.containerPath,\n },\n }),\n };\n return spec;\n }\n\n const spec: ContainerSpec = {\n kind: 'image',\n lambda,\n image: imageRef!,\n platform: platform!,\n command: lambda.imageConfig.command ?? [],\n ...(lambda.imageConfig.entryPoint !== undefined &&\n lambda.imageConfig.entryPoint.length > 0 && {\n entryPoint: lambda.imageConfig.entryPoint,\n }),\n ...(lambda.imageConfig.workingDirectory !== undefined && {\n workingDir: lambda.imageConfig.workingDirectory,\n }),\n env: dockerEnv,\n ...(sensitiveEnvKeys && { sensitiveEnvKeys }),\n containerHost,\n ...(debugPort !== undefined && { debugPort }),\n ...(tmpfs !== undefined && { tmpfs }),\n ...(profileCredsFile && {\n profileCredentialsFile: {\n hostPath: profileCredsFile.hostPath,\n containerPath: profileCredsFile.containerPath,\n },\n }),\n };\n return spec;\n}\n\n/**\n * Resolve a container Lambda's local docker image — local build from\n * `cdk.out` asset manifest first, ECR-pull fallback when the asset\n * manifest has no matching entry. Mirrors `cdkl invoke`'s\n * `resolveContainerImagePlan` shape; the start-api server doesn't\n * need the no-build flag (deterministic-tag cache reuse is automatic\n * across reloads because the per-Lambda tag is content-addressed).\n *\n * Same-account / same-region only on the ECR-pull path (matches the\n * `cdkl invoke` PR 5 of #224 boundary). Cross-account /\n * cross-region ECR pull is the W2-1 deferred follow-up.\n */\nexport async function resolveContainerImageForStartApi(\n lambda: ResolvedStartApiImageLambda,\n skipPull: boolean,\n profile?: string\n): Promise<{ imageRef: string }> {\n const logger = getLogger();\n const localBuild = await resolveLocalBuildPlan(lambda);\n if (localBuild) {\n const imageRef = await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {\n architecture: lambda.architecture,\n });\n return { imageRef };\n }\n if (!parseEcrUri(lambda.imageUri)) {\n throw new Error(\n `Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri ` +\n `'${lambda.imageUri}' is not an ECR URI ${getEmbedConfig().binaryName} can authenticate against. ` +\n 'Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.'\n );\n }\n logger.info(\n `No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull (same-acct/region only)...`\n );\n const imageRef = await pullEcrImage(lambda.imageUri, {\n skipPull,\n ...(profile !== undefined && { profile }),\n });\n return { imageRef };\n}\n\n/**\n * Look up the docker image asset that backs a container Lambda.\n * Returns `undefined` when the asset manifest has no matching entry —\n * the caller falls back to the ECR-pull path.\n *\n * Mirrors `local-invoke.ts:resolveLocalBuildPlan`; kept separate so\n * the two commands evolve their asset-lookup heuristics independently.\n */\nasync function resolveLocalBuildPlan(\n lambda: ResolvedStartApiImageLambda\n): Promise<{ asset: { source: DockerImageAssetSource }; cdkOutDir: string } | undefined> {\n const manifestPath = lambda.stack.assetManifestPath;\n if (!manifestPath) return undefined;\n const cdkOutDir = path.dirname(manifestPath);\n const loader = new AssetManifestLoader();\n const manifest = await loader.loadManifest(cdkOutDir, lambda.stack.stackName);\n if (!manifest) return undefined;\n const entry = getDockerImageBySourceHash(manifest, lambda.imageUri);\n if (!entry) return undefined;\n return { asset: entry.asset, cdkOutDir };\n}\n\n/**\n * Build the `/opt` bind-mount source for a Lambda's layers. Mirrors\n * the helper in `src/cli/commands/local-invoke.ts` but stores the\n * merged tmpdir into the shared `layerTmpDirs` set so the server's\n * graceful shutdown path can clean it up. Returns `undefined` when\n * the function declares no layers.\n *\n * Three branches:\n * - 0 layers → `undefined` (no `/opt` mount).\n * - 1 layer → bind-mount the layer's asset dir directly (no copy)\n * when the entry is a same-stack asset. Literal-ARN entries always\n * pre-materialize first.\n * - 2+ layers → copy each into a fresh tmpdir IN ORDER (later\n * layers overwrite earlier files via `cpSync({force: true})`),\n * bind-mount the tmpdir at `/opt`. Records the tmpdir in\n * `layerTmpDirs` so `shutdown(...)` removes it.\n *\n * Issue #448: literal-ARN entries (`{kind: 'arn', ...}`) are downloaded\n * + unzipped via `lambda:GetLayerVersion` BEFORE the cpSync-merge\n * branches run. Every per-ARN tmpdir is also recorded in `layerTmpDirs`\n * so the same shutdown path cleans it up — even for the single-layer\n * fast path that bind-mounts the dir directly.\n *\n * AWS Lambda's actual runtime extracts every layer ZIP into `/opt`\n * in template order — the merge mirrors that. Docker rejects multiple\n * `-v ...:/opt:ro` entries at the same target, so cdk-local can't rely on\n * overlay layering and must produce a single merged dir on the host.\n */\nexport async function materializeLambdaLayers(\n layers: ResolvedLambdaLayer[],\n layerTmpDirs: Set<string>,\n layerRoleArn: string | undefined\n): Promise<string | undefined> {\n if (layers.length === 0) return undefined;\n\n // Stage 1: pre-materialize every literal-ARN entry into its own\n // tmpdir (issue #448). The resulting flat list of `assetPath`\n // entries is what the existing single-layer / multi-layer merge\n // branches consume.\n const flat: { logicalId: string; assetPath: string }[] = [];\n for (const layer of layers) {\n if (layer.kind === 'asset') {\n flat.push({ logicalId: layer.logicalId, assetPath: layer.assetPath });\n continue;\n }\n const dir = await materializeLayerFromArn(layer, {\n ...(layerRoleArn !== undefined && { roleArn: layerRoleArn }),\n });\n layerTmpDirs.add(dir);\n flat.push({ logicalId: layer.arn, assetPath: dir });\n }\n\n if (flat.length === 1) return flat[0]!.assetPath;\n const dir = mkdtempSync(\n path.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-start-api-layers-`)\n );\n for (const layer of flat) {\n // `recursive: true` enables the directory copy. `force: true`\n // implements AWS's \"last layer wins\" file-collision semantic: a\n // later layer's entry at the same relative path overwrites the\n // earlier one.\n //\n // **Contract pinned (Node 20+)**: this call relies on `fs.cpSync`\n // defaults that the integ-test fixture (`tests/integration/local-\n // invoke-layers/`) exercises end-to-end, and that future\n // refactors must NOT silently drop:\n // - `mode` defaults to preserving the source's file-mode bits,\n // including `+x`. AWS layers commonly ship executable scripts\n // under `bin/` and a handler that runs `/opt/bin/<script>`\n // would otherwise fail with \"Permission denied\".\n // - `verbatimSymlinks` defaults to true on Node 20+; symlinks\n // are copied as symlinks (not dereferenced), matching AWS's\n // layer-ZIP extraction into `/opt`.\n // Mirrors the same contract pinned in `local-invoke.ts`'s\n // `materializeLambdaLayers`; keep the two call sites in sync if\n // they ever consolidate into one helper.\n cpSync(layer.assetPath, dir, { recursive: true, force: true });\n }\n layerTmpDirs.add(dir);\n return dir;\n}\n\n/**\n * Locate a Lambda by logical ID across the target stacks. Throws when\n * no stack contains a matching `AWS::Lambda::Function` — at this point\n * route discovery has already linked the routes to logical IDs, so a\n * miss here is a synthesis bug worth surfacing.\n */\n/**\n * Discriminated union covering both ZIP and container Lambdas (closes\n * #453). The ZIP variant is the original `cdkl start-api` v1\n * shape; the IMAGE variant was unlocked here in this PR — the per-\n * Lambda image build / ECR pull / `--platform` threading lives below\n * in `buildContainerSpec`'s `kind === 'image'` branch.\n */\ntype ResolvedStartApiLambda = ResolvedStartApiZipLambda | ResolvedStartApiImageLambda;\n\ninterface ResolvedStartApiLambdaBase {\n stack: StackInfo;\n logicalId: string;\n resource: TemplateResource;\n memoryMb: number;\n timeoutSec: number;\n /**\n * Resolved same-stack `Properties.Layers` references. Populated on\n * the ZIP branch; always `[]` on the IMAGE branch — container\n * Lambdas reject `Layers` at deploy time on the AWS side (layers\n * are baked into the image at build time, not overlaid at\n * runtime), so cdk-local silently ignores any `Layers` property to\n * match AWS's invoke-time behavior. The base-shape `[]` keeps the\n * ResolvedImageLambda → `lambda-resolver.ResolvedImageLambda`\n * cast structurally valid in the container-pool spec.\n */\n layers: ResolvedLambdaLayer[];\n /**\n * `Properties.EphemeralStorage.Size` (issue #440), MiB. Parsed via\n * the shared `extractEphemeralStorageMb` helper so the CFn-range\n * validation (reject > 10240) matches `cdkl invoke`. Plumbed\n * into the warm container's `--tmpfs /tmp:size=Nm` so handlers in\n * the start-api server see the same sized `/tmp` cap they would on\n * the deployed function. Applies to BOTH ZIP and IMAGE Lambdas —\n * Docker `--tmpfs` overlays inside any container image just like on\n * the public base images. Undefined when the property is absent.\n */\n ephemeralStorageMb?: number;\n}\n\ninterface ResolvedStartApiZipLambda extends ResolvedStartApiLambdaBase {\n kind: 'zip';\n runtime: string;\n handler: string;\n codePath: string | null;\n inlineCode?: string;\n}\n\nexport interface ResolvedStartApiImageLambda extends ResolvedStartApiLambdaBase {\n kind: 'image';\n /**\n * Raw `Code.ImageUri` value, surfaced for the local-build path's\n * asset-hash extraction AND for the ECR-pull fallback. Already\n * resolved through cdk-assets bootstrap-placeholder substitution\n * upstream — `${AWS::*}` pseudo-parameters are still present and\n * substituted at the lookup site.\n */\n imageUri: string;\n /**\n * `ImageConfig` (all fields optional). Most container Lambdas leave\n * `EntryPoint` unset so `/lambda-entrypoint.sh` stays in charge of\n * RIE dispatch. `Command` is typically the handler reference, e.g.\n * `['app.handler']`.\n */\n imageConfig: {\n command?: string[];\n entryPoint?: string[];\n workingDirectory?: string;\n };\n /**\n * `Architectures: [x86_64]` (default) or `[arm64]`. Threaded through\n * to `--platform linux/amd64` / `linux/arm64` on BOTH `docker build`\n * AND `docker run` so an arm64 host running an x86_64 Lambda doesn't\n * hit silent emulation, and an x86_64 host running an arm64 Lambda\n * doesn't fail with `exec format error`.\n */\n architecture: 'x86_64' | 'arm64';\n}\n\nexport function resolveLambdaByLogicalId(\n logicalId: string,\n stacks: StackInfo[]\n): ResolvedStartApiLambda {\n for (const stack of stacks) {\n const resource = stack.template.Resources?.[logicalId];\n if (!resource || resource.Type !== 'AWS::Lambda::Function') continue;\n const props = resource.Properties ?? {};\n const memoryMb = typeof props['MemorySize'] === 'number' ? props['MemorySize'] : 128;\n const timeoutSec = typeof props['Timeout'] === 'number' ? props['Timeout'] : 3;\n\n const code = (props['Code'] ?? {}) as Record<string, unknown>;\n const imageUri = extractImageUri(\n code['ImageUri'],\n logicalId,\n stack.stackName,\n stack.template.Resources ?? {},\n stack.region\n );\n if (imageUri !== undefined) {\n return resolveImageLambda({\n stack,\n logicalId,\n resource,\n props,\n memoryMb,\n timeoutSec,\n imageUri,\n });\n }\n\n // ZIP branch: Runtime + Handler mandatory.\n const runtime = typeof props['Runtime'] === 'string' ? props['Runtime'] : '';\n const handler = typeof props['Handler'] === 'string' ? props['Handler'] : '';\n if (!runtime) {\n throw new Error(\n `Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. ` +\n `${getEmbedConfig().cliName} start-api cannot tell if this is a ZIP or a container Lambda.`\n );\n }\n if (!handler) {\n throw new Error(`Lambda '${logicalId}' has no Handler property.`);\n }\n const inlineCode = typeof code['ZipFile'] === 'string' ? code['ZipFile'] : undefined;\n let codePath: string | null = null;\n if (!inlineCode) {\n codePath = resolveAssetCodePath(stack, logicalId, resource);\n }\n // PR 6 (#232): same-stack `Properties.Layers` references resolve to\n // local asset directories that bind-mount at `/opt`; start-api\n // routes through the same lambda-resolver helper as `cdk-local local\n // invoke` so the warm container pool gets layer support out of\n // the box.\n const layers = resolveLambdaLayers(stack, logicalId, props);\n const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);\n return {\n kind: 'zip',\n stack,\n logicalId,\n resource,\n runtime,\n handler,\n memoryMb,\n timeoutSec,\n codePath,\n layers,\n ...(inlineCode !== undefined && { inlineCode }),\n ...(ephemeralStorageMb !== undefined && { ephemeralStorageMb }),\n };\n }\n throw new Error(\n `No AWS::Lambda::Function resource named '${logicalId}' found in target stacks. This is likely a synthesis bug — the route-discovery phase resolved a route to this logical ID.`\n );\n}\n\n/**\n * Extract `Code.ImageUri` across the shapes CDK actually synthesizes.\n * Mirrors the simpler subset of `lambda-resolver.ts:extractImageUri`\n * scoped to the shapes `cdkl start-api` consumes — flat string,\n * `Fn::Sub` (the canonical asset shape for\n * `lambda.DockerImageCode.fromImageAsset`), and `Fn::Join` (the\n * canonical shape for `lambda.DockerImageCode.fromImageAsset` in\n * CDK 2.x, which emits a `Fn::Join` over the literal bootstrap ECR\n * URI with `${AWS::URLSuffix}` — issues #627 + #637).\n *\n * The `Fn::Join` arm routes through the shared\n * `tryResolveImageFnJoin` helper (`src/local/intrinsic-image.ts`) used\n * by `cdkl invoke`. When the synth template recorded a deploy\n * region (`stack.region`), we derive `{ urlSuffix, partition, region }`\n * via `derivePseudoParametersFromRegion` and pass it as the resolver's\n * `pseudoParameters` block so the canonical `${AWS::URLSuffix}` shape\n * resolves cleanly (issue #637). Same-stack ECR refs still surface as\n * `needs-state` (those require `--from-state`); a Join that references\n * `${AWS::AccountId}` without state still surfaces as `not-applicable`\n * with a more specific error message naming the missing parameter.\n *\n * Returns `undefined` when the field is absent or non-recognized,\n * which routes the caller to the ZIP branch (with its existing\n * \"no Runtime / no Handler\" validations).\n */\nfunction extractImageUri(\n value: unknown,\n logicalId: string,\n stackName: string,\n resources: Record<string, TemplateResource>,\n region: string | undefined\n): string | undefined {\n if (typeof value === 'string' && value.length > 0) return value;\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const obj = value as Record<string, unknown>;\n const sub = obj['Fn::Sub'];\n if (typeof sub === 'string' && sub.length > 0) return sub;\n if (Array.isArray(sub) && typeof sub[0] === 'string') return sub[0];\n\n if ('Fn::Join' in obj) {\n // Issue #637: derive the region-knowable pseudo parameters\n // (`urlSuffix` / `partition` / `region`) and pass them as\n // resolver context so the canonical `lambda.DockerImageCode\n // .fromImageAsset` shape resolves without `--from-state`.\n // `accountId` stays undefined — the canonical shape has it\n // as a literal in the template, and a Join that genuinely\n // references `${AWS::AccountId}` falls back to the\n // `not-applicable` branch below with a clear error.\n const pseudoParameters = derivePseudoParametersFromRegion(region);\n const joinResolved = tryResolveImageFnJoin(\n value,\n resources,\n pseudoParameters ? { pseudoParameters } : undefined\n );\n if (joinResolved.kind === 'resolved') return joinResolved.uri;\n if (joinResolved.kind === 'needs-state') {\n throw new Error(\n `Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. ` +\n `${getEmbedConfig().cliName} start-api cannot resolve the repository URI without state — ` +\n 'deploy the stack first, rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.'\n );\n }\n if (joinResolved.kind === 'unsupported-join') {\n throw new Error(\n `Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. ` +\n `${getEmbedConfig().cliName} start-api recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape ` +\n '(delimiter \"\" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).'\n );\n }\n // `not-applicable` — the Join couldn't reduce every element\n // AND there's no same-stack ECR Repository ref. With #637's\n // pseudo-parameter plumbing the typical remaining cause is\n // `${AWS::AccountId}` (needs an STS call or `--from-state`)\n // or an unknown region (`stack.region` was undefined so we\n // couldn't derive `urlSuffix`). Either way, surface a clear\n // error instead of falling through to ZIP-branch validation.\n const accountIdHint = pseudoParameters\n ? ` (likely \\${AWS::AccountId}, which ${getEmbedConfig().binaryName} cannot derive without a state source or STS)`\n : ` (${getEmbedConfig().binaryName} could not derive AWS pseudo parameters because stack.region was undefined)`;\n throw new Error(\n `Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that ${getEmbedConfig().cliName} start-api cannot resolve${accountIdHint}. ` +\n 'Workarounds: deploy first and run with a state-source flag (e.g. --from-cfn-stack or a host-provided extension), or pin a fully-literal public image URI.'\n );\n }\n }\n return undefined;\n}\n\n/**\n * Build the IMAGE-variant `ResolvedStartApiLambda` from a Lambda\n * template entry with `Code.ImageUri`. Mirrors\n * `lambda-resolver.ts:extractImageLambdaProperties` but trimmed to the\n * fields `cdkl start-api` actually consumes.\n */\nfunction resolveImageLambda(args: {\n stack: StackInfo;\n logicalId: string;\n resource: TemplateResource;\n props: Record<string, unknown>;\n memoryMb: number;\n timeoutSec: number;\n imageUri: string;\n}): ResolvedStartApiImageLambda {\n const { stack, logicalId, resource, props, memoryMb, timeoutSec, imageUri } = args;\n\n const rawImageConfig = (props['ImageConfig'] ?? {}) as Record<string, unknown>;\n const imageConfig: ResolvedStartApiImageLambda['imageConfig'] = {};\n if (Array.isArray(rawImageConfig['Command'])) {\n imageConfig.command = rawImageConfig['Command'].filter(\n (s): s is string => typeof s === 'string'\n );\n }\n if (Array.isArray(rawImageConfig['EntryPoint'])) {\n imageConfig.entryPoint = rawImageConfig['EntryPoint'].filter(\n (s): s is string => typeof s === 'string'\n );\n }\n if (typeof rawImageConfig['WorkingDirectory'] === 'string') {\n imageConfig.workingDirectory = rawImageConfig['WorkingDirectory'];\n }\n\n // Architectures defaults to x86_64. CDK only ever sets one entry.\n const arches = props['Architectures'];\n let architecture: 'x86_64' | 'arm64' = 'x86_64';\n if (Array.isArray(arches) && arches.length > 0) {\n const first: unknown = arches[0];\n if (first === 'arm64') architecture = 'arm64';\n else if (first === 'x86_64') architecture = 'x86_64';\n else {\n throw new Error(\n `Lambda '${logicalId}' has unsupported Architectures value '${String(first)}'. ` +\n `${getEmbedConfig().cliName} start-api supports x86_64 and arm64.`\n );\n }\n }\n\n // Issue #440 — EphemeralStorage.Size applies to container Lambdas\n // too; AWS accepts it on `lambda.DockerImageFunction`. Parse via the\n // shared helper for CFn-range validation parity with the ZIP branch.\n const ephemeralStorageMb = extractEphemeralStorageMb(props, logicalId);\n\n return {\n kind: 'image',\n stack,\n logicalId,\n resource,\n memoryMb,\n timeoutSec,\n imageUri,\n imageConfig,\n architecture,\n layers: [],\n ...(ephemeralStorageMb !== undefined && { ephemeralStorageMb }),\n };\n}\n\n/**\n * Locate the Lambda's local code directory using the CDK-blessed\n * `Metadata['aws:asset:path']` hint. Bind-mounted directly at\n * `/var/task` (read-only) by the docker-runner.\n */\nfunction resolveAssetCodePath(\n stack: StackInfo,\n logicalId: string,\n resource: TemplateResource\n): string {\n const meta = resource.Metadata;\n const assetPath = meta?.['aws:asset:path'];\n if (typeof assetPath !== 'string' || assetPath.length === 0) {\n throw new Error(\n `Lambda '${logicalId}' has no Metadata['aws:asset:path']. ${getEmbedConfig().cliName} start-api needs this hint to find the local asset directory. Re-synthesize the app and retry.`\n );\n }\n const cdkOutDir = stack.assetManifestPath ? path.dirname(stack.assetManifestPath) : process.cwd();\n return path.isAbsolute(assetPath) ? assetPath : path.resolve(cdkOutDir, assetPath);\n}\n\n/**\n * Print the discovered route table to stdout. Format mirrors the spec\n * doc's example so verify.sh / users can read it at a glance.\n *\n * Routes with `unsupported` or `mockCors` are annotated so the user can\n * tell at a glance which routes will dispatch to a Lambda vs which\n * return 501 / 204 directly:\n * - normal: `GET /items -> Handler (HTTP API)`\n * - mockCors: `OPTIONS /items -> [MOCK CORS preflight] (REST v1, stage 'prod')`\n * - unsupported: `POST /admin -> [501 Not Implemented] (HTTP API)`\n */\nfunction printRouteTable(routes: readonly RouteWithAuth[]): void {\n const flat = routes.map((r) => r.route);\n const sorted = [...flat].sort((a, b) => {\n if (a.pathPattern !== b.pathPattern) return a.pathPattern.localeCompare(b.pathPattern);\n return a.method.localeCompare(b.method);\n });\n const methodWidth = Math.max(...sorted.map((r) => r.method.length), 6);\n const pathWidth = Math.max(...sorted.map((r) => r.pathPattern.length), 8);\n process.stdout.write('Discovered routes:\\n');\n for (const r of sorted) {\n const sourceLabel =\n r.source === 'http-api'\n ? 'HTTP API'\n : r.source === 'rest-v1'\n ? `REST v1, stage '${r.stage}'`\n : 'Function URL';\n const target = r.mockCors\n ? '[MOCK CORS preflight]'\n : r.unsupported\n ? '[501 Not Implemented]'\n : r.serviceIntegration\n ? `[${r.serviceIntegration.subtype}]`\n : r.restV1Integration\n ? formatRestV1IntegrationLabel(r.restV1Integration)\n : r.lambdaLogicalId;\n process.stdout.write(\n ` ${r.method.padEnd(methodWidth)} ${r.pathPattern.padEnd(pathWidth)} -> ${target} (${sourceLabel})\\n`\n );\n }\n process.stdout.write('\\n');\n}\n\n/**\n * Format the route-table label for a REST v1 non-AWS_PROXY integration.\n * `MOCK` / `HTTP` / `HTTP_PROXY` show their integration kind directly;\n * `AWS` (Lambda non-proxy) shows the Lambda logical id with an `[AWS]`\n * suffix so it's distinguishable from AWS_PROXY rows. Closes #457.\n */\nfunction formatRestV1IntegrationLabel(\n integration: NonNullable<DiscoveredRoute['restV1Integration']>\n): string {\n switch (integration.kind) {\n case 'mock':\n return '[MOCK]';\n case 'http-proxy':\n return `[HTTP_PROXY ${integration.uri}]`;\n case 'http':\n return `[HTTP ${integration.uri}]`;\n case 'aws-lambda':\n return `${integration.lambdaLogicalId} [AWS]`;\n }\n}\n\n/**\n * Materialize an inline Lambda body (`Code.ZipFile`) to a tmpdir and\n * return the directory the container should mount at /var/task.\n * Mirrors `cdkl invoke`'s implementation; the only divergence is\n * the long-running-server lifecycle: every tmpdir created here is\n * recorded in `tmpDirsOut` so the caller's shutdown path can `rmSync`\n * them. (`cdkl invoke` runs once and `--rm` is the right model;\n * `cdkl start-api` lives across requests, so leaks compound.)\n */\nfunction materializeInlineCode(\n handler: string,\n source: string,\n fileExtension: string,\n tmpDirsOut: Set<string>\n): string {\n const lastDot = handler.lastIndexOf('.');\n if (lastDot <= 0) {\n throw new Error(`Handler '${handler}' is malformed: expected '<modulePath>.<exportName>'.`);\n }\n const modulePath = handler.substring(0, lastDot);\n const dir = mkdtempSync(path.join(tmpdir(), `${getEmbedConfig().resourceNamePrefix}-start-api-`));\n tmpDirsOut.add(dir);\n const filePath = path.join(dir, `${modulePath}${fileExtension}`);\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(filePath, source, 'utf-8');\n return dir;\n}\n\n/** Pull `Properties.Environment.Variables` (when present). */\nfunction getTemplateEnv(resource: {\n Properties?: Record<string, unknown>;\n}): Record<string, unknown> | undefined {\n const props = resource.Properties ?? {};\n const env = props['Environment'];\n if (!env || typeof env !== 'object') return undefined;\n const vars = (env as Record<string, unknown>)['Variables'];\n if (!vars || typeof vars !== 'object') return undefined;\n return vars as Record<string, unknown>;\n}\n\n/** Read the SAM-shape `--env-vars` JSON file. */\nfunction readEnvOverridesFile(filePath: string | undefined): EnvOverrideFile | undefined {\n if (!filePath) return undefined;\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch (err) {\n throw new Error(\n `Failed to read --env-vars file '${filePath}': ${err instanceof Error ? err.message : String(err)}`\n );\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new Error(\n `Failed to parse --env-vars file '${filePath}' as JSON: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(`--env-vars file '${filePath}' must contain a JSON object at the top level.`);\n }\n return parsed as EnvOverrideFile;\n}\n\n/**\n * Forward the developer's AWS credentials into the container so the\n * handler's AWS SDK calls can authenticate. Used when --assume-role is\n * NOT set for that Lambda — SAM-compatible default.\n */\nfunction forwardAwsEnv(env: Record<string, string>): void {\n const passThrough = [\n 'AWS_ACCESS_KEY_ID',\n 'AWS_SECRET_ACCESS_KEY',\n 'AWS_SESSION_TOKEN',\n 'AWS_REGION',\n 'AWS_DEFAULT_REGION',\n ] as const;\n for (const key of passThrough) {\n const value = process.env[key];\n if (value !== undefined) env[key] = value;\n }\n}\n\n/**\n * Issue #654: resolve `--profile <p>` to a concrete credential set\n * for forwarding to Lambda containers.\n *\n * The dev's AWS credentials may live in any of:\n * - `~/.aws/sso/cache/*.json` (AWS IAM Identity Center / legacy SSO)\n * - `~/.aws/credentials` (regular long-lived access keys)\n * - `~/.aws/config` profiles with `role_arn` + `source_profile` (chained AssumeRole)\n * - `credential_process` external resolvers\n *\n * `forwardAwsEnv` only reads `process.env.AWS_*`, which is empty for\n * every shape except \"user manually exported the env vars\". The\n * Lambda container therefore boots without creds and the handler's\n * AWS SDK call fails with `Could not load credentials from any providers`.\n *\n * This helper constructs a transient `STSClient({ profile })` to drive\n * the SDK's default credential provider chain — same code path the\n * host's own AWS SDK clients use when `--profile` is set, so SSO / IAM\n * Identity Center / role-assumption profiles all resolve the same way\n * they already do for the host's outbound calls. We then extract the\n * resolved `AwsCredentialIdentity` via `sts.config.credentials()` and\n * return the underlying `{ accessKeyId, secretAccessKey, sessionToken? }`\n * for env-var injection.\n *\n * Called ONCE at server boot; the resolved creds are reused for every\n * Lambda container's env overlay (when `--assume-role` is not set for\n * that Lambda — assume-role wins per the existing precedence). SSO\n * temp creds typically last 1h+, so a single resolve is fine for the\n * common dev session; long-running `--watch` sessions that outlive\n * the creds need a cdk-local restart (deferred refresh out of scope for\n * v1, see issue #654).\n *\n * Also resolves the profile's configured `region` (from `~/.aws/config`'s\n * `region = ...` for this profile) off the same client config. The\n * synthesized credentials file we mount into the container carries only\n * the credential triple — no `region =` — so without this the container\n * boots with creds but no region, and a handler's ambient-region SDK call\n * (`new XxxClient({})`) fails with \"Region is missing\". The caller seeds\n * the container's `AWS_REGION` fallback with it (see\n * {@link resolveContainerFallbackRegion}). Region resolution is\n * best-effort: a profile with no region (or any resolution failure)\n * yields `region: undefined` rather than throwing — a missing region is\n * not a missing-credentials error.\n */\nexport async function resolveProfileCredentials(profile: string): Promise<{\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n region?: string;\n}> {\n const { STSClient } = await import('@aws-sdk/client-sts');\n const sts = new STSClient({ profile });\n try {\n const credsProvider = sts.config.credentials;\n const creds = typeof credsProvider === 'function' ? await credsProvider() : credsProvider;\n if (!creds || !creds.accessKeyId || !creds.secretAccessKey) {\n throw new Error(\n `--profile '${profile}': credential provider chain resolved without usable credentials. ` +\n 'Check `aws sso login --profile ' +\n profile +\n '` for SSO profiles, or `~/.aws/credentials` / `~/.aws/config` for regular profiles.'\n );\n }\n let region: string | undefined;\n try {\n const regionProvider = sts.config.region;\n const resolved =\n typeof regionProvider === 'function' ? await regionProvider() : regionProvider;\n if (typeof resolved === 'string' && resolved.length > 0) region = resolved;\n } catch {\n // Profile has no region configured (and no AWS_REGION env) — leave\n // undefined; the container falls through to the next region source.\n region = undefined;\n }\n return {\n accessKeyId: creds.accessKeyId,\n secretAccessKey: creds.secretAccessKey,\n ...(creds.sessionToken && { sessionToken: creds.sessionToken }),\n ...(region && { region }),\n };\n } finally {\n sts.destroy();\n }\n}\n\n/**\n * Resolve the fallback `AWS_REGION` for a Lambda container when neither\n * the assume-role STS region nor a forwarded `AWS_REGION` /\n * `AWS_DEFAULT_REGION` env var already set one.\n *\n * Precedence mirrors where the deployed function would actually run, so a\n * handler's ambient-region SDK call (`new XxxClient({})`) reaches the same\n * region locally as the resources it is bound to:\n *\n * 1. `--stack-region` — explicit; also drives the `--from-cfn-stack` CFn\n * client, so the container matches the region the bound stack was read\n * from.\n * 2. the synth-derived stack region — `env.region` on the CDK stack, read\n * from the cloud assembly manifest (`StackInfo.region`). Previously\n * used only host-side for the `--from-cfn-stack` CFn client; never\n * injected into the container.\n * 3. the `--profile`'s configured region — `~/.aws/config`'s `region =`\n * for the profile. `--profile` injected credentials but not this.\n *\n * Returns `undefined` when none is known; the caller leaves `AWS_REGION`\n * unset so the SDK's own \"Region is missing\" surfaces (the correct signal\n * for a region-agnostic stack with no profile region and no `AWS_REGION`\n * env).\n */\nexport function resolveContainerFallbackRegion(args: {\n stackRegionOverride?: string | undefined;\n synthRegion?: string | undefined;\n profileRegion?: string | undefined;\n}): string | undefined {\n return args.stackRegionOverride ?? args.synthRegion ?? args.profileRegion;\n}\n\n/**\n * Issue an STS AssumeRole and return temporary credentials. Mirrors\n * `cdkl invoke`'s helper byte-for-byte; lifted here so the\n * start-api command stays self-contained.\n */\nasync function assumeLambdaExecutionRole(\n roleArn: string,\n region: string | undefined\n): Promise<{ accessKeyId: string; secretAccessKey: string; sessionToken: string }> {\n const { STSClient, AssumeRoleCommand } = await import('@aws-sdk/client-sts');\n const sts = new STSClient({ ...(region && { region }) });\n try {\n const response = await sts.send(\n new AssumeRoleCommand({\n RoleArn: roleArn,\n RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-start-api-${Date.now()}`,\n DurationSeconds: 3600,\n })\n );\n const creds = response.Credentials;\n if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) {\n throw new Error(`AssumeRole(${roleArn}) returned no usable credentials.`);\n }\n return {\n accessKeyId: creds.AccessKeyId,\n secretAccessKey: creds.SecretAccessKey,\n sessionToken: creds.SessionToken,\n };\n } finally {\n sts.destroy();\n }\n}\n\n/**\n * Parse / clamp the `--per-lambda-concurrency` flag. Above-cap values\n * are clamped to 4 with a warn line (per the spec doc's risk-mitigation\n * row).\n */\nfunction parsePerLambdaConcurrency(raw: string): number {\n const parsed = parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < 1) {\n throw new Error(`--per-lambda-concurrency must be a positive integer (got '${raw}')`);\n }\n if (parsed > 4) {\n getLogger().warn(\n `--per-lambda-concurrency ${parsed} exceeds the v1 cap of 4; clamping to 4. (Raise this in a follow-up PR if your workload needs more.)`\n );\n return 4;\n }\n return parsed;\n}\n\n/**\n * One booted HTTP server tied to a single API surface (issue #260).\n * The CLI keeps an array of these to drive per-server route tables,\n * shutdown, and hot-reload state swaps.\n *\n * `group` is intentionally **mutable** — hot-reload swaps the group\n * in place after `setServerState` so post-reload route-table reprints\n * (`printPerServerRouteTables(servers)`) reflect the new routes,\n * including any new `mockCors` / `unsupported` classifications.\n * Pre-fix the field was readonly and the printed table after reload\n * always showed the boot-time routes.\n */\ninterface BootedApiServer {\n group: ApiServerGroup;\n readonly server: StartedApiServer;\n}\n\n/**\n * One booted WebSocket API server (#462). Mirrors `BootedApiServer`\n * for the upgrade-event-driven WebSocket family. Each instance owns:\n *\n * - `server`: the underlying `node:http` (or `https` under mTLS)\n * server hosting both the upgrade-listener AND the\n * `@connections/<id>` HTTP data plane via the preDispatch hook.\n * - `attached`: the WebSocket-server wrapper (connection registry +\n * close handle). The HTTP server's preDispatch closure reads the\n * registry to route management-API POST calls.\n * - `apiPath`: the URL path the upgrade request must match\n * (default `/<stage>` so multi-API setups stay disambiguated).\n *\n * Hot reload (`--watch`) for WebSocket APIs is restart-only in v1 —\n * see design doc §12 Q5. The CLI surfaces a warn line when a watched\n * file change implies a WebSocket route / Lambda change so the user\n * knows to restart.\n */\ninterface BootedWebSocketServer {\n api: DiscoveredWebSocketApi;\n readonly server: StartedApiServer;\n readonly attached: AttachedWebSocketServer;\n readonly apiPath: string;\n}\n\n/**\n * Filter the global Lambda spec map to just the Lambdas reachable from\n * one API server group. The container pool for that server is built\n * from this filtered map so per-API authorizer Lambdas + route\n * handlers stay scoped to their owning server — disposing one server's\n * pool on shutdown does NOT touch another server's still-warm\n * containers.\n *\n * Also includes any authorizer Lambdas attached to the group's routes\n * (a Lambda authorizer is a Lambda the pool needs to know about, even\n * though no route directly handles `lambdaLogicalId === auth.lambdaLogicalId`).\n */\nfunction filterSpecsForGroup(\n group: ApiServerGroup,\n allSpecs: Map<string, ContainerSpec>\n): Map<string, ContainerSpec> {\n const ids = new Set<string>();\n for (const rwa of group.routes) {\n ids.add(rwa.route.lambdaLogicalId);\n const auth = rwa.authorizer;\n if (auth && (auth.kind === 'lambda-token' || auth.kind === 'lambda-request')) {\n ids.add(auth.lambdaLogicalId);\n }\n }\n const out = new Map<string, ContainerSpec>();\n for (const id of ids) {\n const spec = allSpecs.get(id);\n if (spec) out.set(id, spec);\n }\n return out;\n}\n\n/**\n * Print one route table per server, with the server's display name as\n * the section header. Replaces the pre-issue #260 single flat table —\n * users now see exactly which routes belong to which API + port.\n */\nfunction printPerServerRouteTables(servers: readonly BootedApiServer[]): void {\n for (const { group, server } of servers) {\n process.stdout.write(`\\n${group.displayName} (http://${server.host}:${server.port})\\n`);\n printRouteTable(group.routes);\n }\n}\n\n/**\n * Surface every `unsupported` route (deferred 501) as a startup warn so\n * the user sees what isn't reachable BEFORE they try to curl it. One\n * warn line per route — the route's `unsupported.reason` already names\n * the offender + the underlying limitation, so we just prefix with\n * method + path. Returns the number of unsupported routes so the caller\n * can emit a single-line summary header above the list.\n */\nfunction warnUnsupportedRoutes(\n routes: readonly DiscoveredRoute[],\n logger: ReturnType<typeof getLogger>\n): number {\n const unsupported = routes.filter((r) => r.unsupported);\n if (unsupported.length === 0) return 0;\n logger.warn(\n `${unsupported.length} route(s) will respond HTTP 501 Not Implemented when hit (boot continued):`\n );\n for (const r of unsupported) {\n logger.warn(` - ${r.method} ${r.pathPattern}: ${r.unsupported!.reason}`);\n }\n return unsupported.length;\n}\n\n/**\n * Surface every WebSocket API tagged as unsupported at discovery as a\n * startup warn. The boot loop above skips attaching the server for\n * these APIs, so no upgrade requests are ever accepted on them —\n * mirrors `warnUnsupportedRoutes`'s shape but for the WebSocket axis.\n * Typical trigger: a Route declaring `AuthorizationType !== 'NONE'` on\n * `$connect` (cdkl v1 does not emulate WebSocket authorizers; closing\n * this gap structurally rather than silently admitting\n * unauthenticated clients matches the security-by-default precedent\n * PR #514 set for HTTP API v2 service integrations).\n */\nfunction warnUnsupportedWebSocketApis(\n apis: readonly DiscoveredWebSocketApi[],\n logger: ReturnType<typeof getLogger>\n): number {\n const unsupported = apis.filter((api) => api.unsupported);\n if (unsupported.length === 0) return 0;\n logger.warn(\n `${unsupported.length} WebSocket API(s) will NOT accept upgrade requests (boot continued):`\n );\n for (const api of unsupported) {\n logger.warn(` - ${api.declaredAt}: ${api.unsupported!.reason}`);\n }\n return unsupported.length;\n}\n\n/**\n * Surface a one-line warn per HTTP / HTTP_PROXY integration whose\n * `Integration.Uri` points at a well-known internal address space\n * (AWS IMDS, loopback, link-local, RFC1918). PR #505 / issue #457\n * follow-up: cdk-local does NOT block these — warn-and-proceed matches the\n * cognito JWKS pass-through pattern — but the user should see the\n * destination at boot so a malicious / typo'd template Uri does not\n * silently exfiltrate credentials in CI. Deduplicated per-Uri.\n */\nfunction warnSsrfRiskyIntegrations(\n routes: readonly DiscoveredRoute[],\n logger: ReturnType<typeof getLogger>\n): void {\n const seen = new Set<string>();\n for (const r of routes) {\n const integ = r.restV1Integration;\n if (!integ) continue;\n if (integ.kind !== 'http' && integ.kind !== 'http-proxy') continue;\n if (seen.has(integ.uri)) continue;\n seen.add(integ.uri);\n warnSsrfRiskyUri(integ.uri, `${r.method} ${r.pathPattern}`, (msg) => logger.warn(msg));\n }\n}\n\n/**\n * One reload cycle for the multi-server topology (issue #260). The\n * watcher serializes calls via a chain promise; this function:\n *\n * 1. Re-runs `synthesizeAndBuild()` once (failure → warn + keep\n * previous version serving on every server).\n * 2. Re-groups the new routes by API server key.\n * 3. For each existing server, swaps state to the new group's\n * routes + a freshly-built pool filtered to that group's\n * Lambdas. Disposes the previous pool in the background.\n * 4. Warns about new groups (= an API was added in CDK code) and\n * vanished groups (= an API was removed) — those require a\n * server restart in v1.\n */\nasync function reloadAllServers(args: {\n synthesizeAndBuild: () => Promise<NextStateMaterial>;\n servers: readonly BootedApiServer[];\n buildPool: (specs: Map<string, ContainerSpec>) => ContainerPool;\n logger: ReturnType<typeof getLogger>;\n}): Promise<void> {\n const { synthesizeAndBuild, servers, buildPool, logger } = args;\n let material: NextStateMaterial;\n try {\n material = await synthesizeAndBuild();\n } catch (err) {\n logger.warn(\n `cdk synth failed during reload; keeping previous version. (${err instanceof Error ? err.message : String(err)})`\n );\n return;\n }\n const newGroups = groupRoutesByServer(material.routes);\n const newByKey = new Map(newGroups.map((g) => [g.serverKey, g] as const));\n const oldKeys = new Set(servers.map((s) => s.group.serverKey));\n const newKeys = new Set(newByKey.keys());\n\n // Warn on add/remove — v1 requires restart for topology changes.\n const added = [...newKeys].filter((k) => !oldKeys.has(k));\n const removed = [...oldKeys].filter((k) => !newKeys.has(k));\n if (added.length > 0) {\n logger.warn(\n `Reload detected new API surface(s): ${added.join(', ')}. Restart '${getEmbedConfig().cliName} start-api' to serve them.`\n );\n }\n if (removed.length > 0) {\n logger.warn(\n `Reload detected removed API surface(s): ${removed.join(', ')}. Their servers will keep serving stale routes until restart.`\n );\n }\n\n // Per-server: filter material → build pool → swap state → dispose old.\n for (const booted of servers) {\n const group = newByKey.get(booted.group.serverKey);\n if (!group) continue; // removed — skip swap, server keeps stale state until restart\n const groupSpecs = filterSpecsForGroup(group, material.specs);\n const newPool = buildPool(groupSpecs);\n const newState: ServerState = {\n routes: group.routes,\n pool: newPool,\n corsConfigByApiId: material.corsConfigByApiId,\n };\n const previousState = booted.server.setServerState(newState);\n // Update the BootedApiServer's `group` in place so post-reload\n // `printPerServerRouteTables(servers)` reads the new routes,\n // including any new mockCors / unsupported classifications. Pre-fix\n // the printed table always reflected the boot-time routes.\n booted.group = group;\n // Dispose the previous pool in the background. `pool.dispose()`\n // waits for in-flight requests to drain (30s per-entry cap).\n void previousState.pool.dispose().catch((err) => {\n logger.debug(\n `Previous pool dispose() failed for ${group.displayName}: ${err instanceof Error ? err.message : String(err)}`\n );\n });\n }\n\n // Re-print the per-server route table when any routes changed.\n // Cheap heuristic: always re-print after a successful reload — the\n // user is watching for the diff and a stable table reassures them\n // that the swap landed. `booted.group` was mutated above so this\n // reflects the post-swap routes (including any new mockCors /\n // unsupported classifications introduced mid-edit).\n printPerServerRouteTables(servers);\n const allRoutes = servers.flatMap((s) => s.group.routes.map((r) => r.route));\n warnUnsupportedRoutes(allRoutes, logger);\n warnSsrfRiskyIntegrations(allRoutes, logger);\n}\n\n/**\n * Per-stack `--from-state` substitution input consumed by\n * {@link buildContainerSpec}. Built once per `synthesizeAndBuild` pass\n * — initial boot AND every hot-reload firing — by\n * {@link loadStateForRoutedStacks}. Empty (no stack-level entry) when\n * the stack's state could not be loaded or the user did not pass\n * `--from-state`.\n */\ninterface StackStateBundle {\n state: StackState;\n /**\n * AWS pseudo parameters (account / region / partition / URL suffix).\n * `undefined` when none of the stack's reachable Lambdas has a\n * pseudo-parameter intrinsic in its env map (skips the STS hop) OR\n * the STS resolution failed (substitution still runs for non-`AWS::*`\n * refs).\n */\n pseudoParameters?: PseudoParameters;\n /**\n * Deploy-time-resolved `Environment.Variables` per reachable Lambda\n * logical ID, fetched while the `--from-cfn-stack` provider was alive.\n * Consumed by `buildContainerSpec` to fill env keys whose intrinsic\n * value the static substituter could not resolve (e.g.\n * `Fn::GetAtt <Sibling>.Arn`). `undefined`/absent for stacks where no\n * provider implements the deployed-env fallback (e.g. `--from-state`).\n */\n deployedEnvByLambda?: Map<string, Record<string, string>>;\n /**\n * Resolved SSM-backed template `Parameters`\n * (`AWS::SSM::Parameter::Value<String>`), keyed by the parameter's\n * logical ID. Fed into the substitution context's `parameters` field so\n * a `Ref` to such a parameter resolves to its SSM value instead of\n * being warn-and-dropped (issue #94). `undefined`/absent when the stack\n * declares no SSM-backed parameters or no provider implements the SSM\n * resolution (e.g. `--from-state`).\n */\n ssmParameters?: Record<string, string>;\n /**\n * Logical IDs of the resolved SSM parameters whose `Type` is\n * `SecureString` (decrypted). Threaded into the substitution context's\n * `sensitiveParameters` so the consuming env keys are routed off the\n * `docker run` argv (issue #99). `undefined`/absent when none.\n */\n ssmSecureStringLogicalIds?: string[];\n}\n\n/**\n * Returns true when any value in the function's template env map is a\n * CFn intrinsic (non-primitive). Used to gate the pseudo-parameter STS\n * hop inside the `--from-state` flow: literal-only env maps don't need\n * the pseudo-parameter bag and shouldn't pay for an STS call. Mirrors\n * the same gating in `local-invoke.ts` (`envHasIntrinsicValue`) and\n * `ecs-task-resolver.ts` (`containerHasIntrinsicEnvOrSecret`).\n */\nexport function envHasIntrinsicValue(templateEnv: Record<string, unknown> | undefined): boolean {\n if (!templateEnv) return false;\n for (const v of Object.values(templateEnv)) {\n if (v === undefined || v === null) continue;\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') continue;\n return true;\n }\n return false;\n}\n\n/**\n * Load deployed state for every stack that owns a routed Lambda. Once\n * per `synthesizeAndBuild` pass (initial boot + every reload), so a\n * Lambda's per-spec build does not pay one round-trip per Lambda. Per-\n * stack failures (no state, ambiguous region, bucket resolution error)\n * degrade to warn-and-fall-back via the active `LocalStateProvider` —\n * the affected stack's reachable Lambdas behave as if `--from-state` /\n * `--from-cfn-stack` were not set, while sibling stacks with loadable\n * state still substitute.\n *\n * Pseudo parameters are resolved per stack and only when at least one\n * reachable Lambda in that stack has an intrinsic-valued env entry\n * (gated via {@link envHasIntrinsicValue}). STS failures degrade to\n * warn and leave `pseudoParameters: undefined` — substitution still\n * runs for non-`AWS::*` refs.\n */\n/**\n * Returns true when at least one host-provided extra state-provider flag\n * is active in the options bag.\n */\nfunction hasExtraStateProviderActive(\n options: LocalStartApiOptions,\n extraStateProviders: ExtraStateProviders | undefined\n): boolean {\n if (!extraStateProviders) return false;\n for (const key of Object.keys(extraStateProviders)) {\n if ((options as unknown as Record<string, unknown>)[key]) return true;\n }\n return false;\n}\n\nexport async function loadStateForRoutedStacks(\n stacks: readonly StackInfo[],\n routes: readonly DiscoveredRoute[],\n routesWithAuth: readonly RouteWithAuth[],\n options: LocalStartApiOptions,\n extraStateProviders: ExtraStateProviders | undefined\n): Promise<Map<string, StackStateBundle>> {\n const logger = getLogger();\n const out = new Map<string, StackStateBundle>();\n\n // Collect the set of (stackName, region) pairs that contain at least\n // one routed Lambda OR an attached authorizer Lambda. The stack\n // resolution mirrors `resolveLambdaByLogicalId`'s walk so we never\n // load state for a stack whose Lambdas are not actually reachable\n // from the discovered API surface.\n const lambdaIds = uniqueLambdaIds(routes, routesWithAuth);\n const reachableStackNames = new Set<string>();\n for (const logicalId of lambdaIds) {\n for (const stack of stacks) {\n const resource = stack.template.Resources?.[logicalId];\n if (resource && resource.Type === 'AWS::Lambda::Function') {\n reachableStackNames.add(stack.stackName);\n break;\n }\n }\n }\n\n // Pre-compute \"does any reachable Lambda in this stack have an\n // intrinsic-valued env map\" — gates the per-stack STS hop.\n const stackHasIntrinsicEnv = (stackName: string): boolean => {\n for (const logicalId of lambdaIds) {\n for (const stack of stacks) {\n if (stack.stackName !== stackName) continue;\n const resource = stack.template.Resources?.[logicalId];\n if (!resource || resource.Type !== 'AWS::Lambda::Function') continue;\n if (envHasIntrinsicValue(getTemplateEnv(resource))) return true;\n }\n }\n return false;\n };\n\n // Issue #606: route through `createLocalStateProvider` so the same\n // code path drives both `--from-state` (S3) and `--from-cfn-stack`\n // (CFn). One provider PER reachable stack — bare `--from-cfn-stack`\n // uses the host stack name per routed stack, so the dispatcher needs\n // each stack's name at construction time. Each provider is disposed\n // after its `load` call returns so the AWS client tied to that\n // provider doesn't outlive the boot pass.\n //\n // Reject explicit `--from-cfn-stack <name>` when multiple host stacks\n // are routed: the explicit name would apply to every routed stack\n // and silently misresolve `Ref` lookups when logical IDs happen to\n // collide between siblings (see local-state-source.ts for the\n // rationale). Bare `--from-cfn-stack` is safe because each routed\n // stack uses its own host stack name.\n rejectExplicitCfnStackWithMultipleStacks(options, reachableStackNames.size);\n for (const stackName of reachableStackNames) {\n const stack = stacks.find((s) => s.stackName === stackName);\n if (!stack) continue;\n const provider = createLocalStateProvider(\n options,\n stack.stackName,\n await resolveCfnFallbackRegion(options, stack.region),\n extraStateProviders\n );\n if (!provider) continue;\n try {\n const loaded = await provider.load(stack.stackName, stack.region);\n if (!loaded) continue;\n\n // Synthesize a `StackState` shape from the provider's\n // `LocalStateRecord` so the downstream `buildContainerSpec` path\n // (which reads `stateBundle.state.resources`) keeps working\n // verbatim. Outputs is an unused field at this call site but\n // populated for completeness; the StackState `version` /\n // `lastModified` carry placeholder values — the\n // sync `substituteEnvVarsFromState` resolver only reads\n // `resources`.\n const syntheticState: StackState = {\n version: 1,\n stackName: stack.stackName,\n resources: loaded.resources,\n outputs: loaded.outputs,\n lastModified: 0,\n };\n const bundle: StackStateBundle = { state: syntheticState };\n if (stackHasIntrinsicEnv(stackName)) {\n const pseudo = await resolvePseudoParametersForStartApi(loaded.region, options);\n if (pseudo) bundle.pseudoParameters = pseudo;\n }\n // Deployed-env fallback source: for each reachable Lambda in this\n // stack whose template env carries an intrinsic, fetch the deployed\n // function's already-resolved `Environment.Variables` while the\n // provider (and its AWS client) is still alive. `buildContainerSpec`\n // later splices these in for keys static substitution dropped (e.g.\n // `Fn::GetAtt <Sibling>.Arn`). Only the CFn provider implements\n // `resolveDeployedFunctionEnv`; `--from-state` carries deploy-time\n // attributes in its state so its GetAtt resolves statically.\n if (provider.resolveDeployedFunctionEnv) {\n const deployedEnvByLambda = new Map<string, Record<string, string>>();\n for (const logicalId of lambdaIds) {\n const resource = stack.template.Resources?.[logicalId];\n if (!resource || resource.Type !== 'AWS::Lambda::Function') continue;\n if (!envHasIntrinsicValue(getTemplateEnv(resource))) continue;\n const physicalId = loaded.resources[logicalId]?.physicalId;\n if (!physicalId) continue;\n const deployedEnv = await provider.resolveDeployedFunctionEnv(physicalId);\n if (deployedEnv) deployedEnvByLambda.set(logicalId, deployedEnv);\n }\n if (deployedEnvByLambda.size > 0) bundle.deployedEnvByLambda = deployedEnvByLambda;\n }\n // Resolve SSM-backed template parameters\n // (`AWS::SSM::Parameter::Value<String>`) once per stack so a `Ref`\n // to such a parameter in a reachable Lambda's env resolves to its\n // SSM value (issue #94). Gated on the stack actually having an\n // intrinsic-valued env entry (same gate as the pseudo-parameter\n // hop) so literal-only stacks skip the SSM round-trip. Only the\n // CFn provider implements `resolveTemplateSsmParameters`.\n if (stackHasIntrinsicEnv(stackName) && provider.resolveTemplateSsmParameters) {\n const ssmParameters = await provider.resolveTemplateSsmParameters(stack.template);\n if (Object.keys(ssmParameters.values).length > 0) {\n bundle.ssmParameters = ssmParameters.values;\n }\n if (ssmParameters.secureStringLogicalIds.length > 0) {\n bundle.ssmSecureStringLogicalIds = ssmParameters.secureStringLogicalIds;\n }\n }\n out.set(stackName, bundle);\n logger.debug(`${provider.label}: loaded state for ${stackName} (${loaded.region})`);\n } finally {\n provider.dispose();\n }\n }\n return out;\n}\n\n/**\n * Build the AWS pseudo-parameter bag for `--from-state` env-var\n * substitution. Mirrors `resolvePseudoParametersForInvoke` in\n * `local-invoke.ts` byte-for-byte — kept inlined here rather than\n * extracted into a shared helper because the two call sites differ in\n * region precedence (this one is per-stack so the resolved state\n * region takes priority).\n *\n * Region precedence: `--region` > `AWS_REGION` > `AWS_DEFAULT_REGION` >\n * the state record's region (returned by the active `LocalStateProvider`).\n */\nasync function resolvePseudoParametersForStartApi(\n stateRegion: string,\n options: LocalStartApiOptions\n): Promise<PseudoParameters | undefined> {\n const logger = getLogger();\n const region =\n options.region ?? process.env['AWS_REGION'] ?? process.env['AWS_DEFAULT_REGION'] ?? stateRegion;\n let accountId: string | undefined;\n try {\n const { STSClient, GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');\n const sts = new STSClient({ ...(region && { region }) });\n try {\n const identity = await sts.send(new GetCallerIdentityCommand({}));\n accountId = identity.Account;\n } finally {\n sts.destroy();\n }\n } catch (err) {\n logger.warn(\n `--from-state: resolver needs \\${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. ` +\n 'Substitution will be skipped for AWS::AccountId; affected env entries will be dropped with per-key warnings.'\n );\n }\n const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : undefined;\n const bag: PseudoParameters = {\n ...(accountId !== undefined && { accountId }),\n ...(region !== undefined && { region }),\n ...(partitionAndSuffix && {\n partition: partitionAndSuffix.partition,\n urlSuffix: partitionAndSuffix.urlSuffix,\n }),\n };\n return Object.keys(bag).length === 0 ? undefined : bag;\n}\n\n/** Validate `--debug-port-base`. */\nfunction parseDebugPort(raw: string): number {\n const parsed = parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {\n throw new Error(`--debug-port-base must be 1..65535 (got '${raw}')`);\n }\n return parsed;\n}\n\n/**\n * Resolve the mTLS configuration from CLI options. Returns `undefined`\n * when none of the three `--mtls-*` flags is set (the server stays\n * plain-HTTP). When any of the three is set, ALL THREE must be set —\n * partial configurations are rejected at parse time so the server\n * never boots in a half-configured state.\n *\n * Exported for unit testing.\n */\nexport function resolveMtlsConfig(\n options: Pick<LocalStartApiOptions, 'mtlsTruststore' | 'mtlsCert' | 'mtlsKey'>\n): MtlsServerConfig | undefined {\n const present: string[] = [];\n const absent: string[] = [];\n if (options.mtlsTruststore !== undefined && options.mtlsTruststore !== '') {\n present.push('--mtls-truststore');\n } else {\n absent.push('--mtls-truststore');\n }\n if (options.mtlsCert !== undefined && options.mtlsCert !== '') {\n present.push('--mtls-cert');\n } else {\n absent.push('--mtls-cert');\n }\n if (options.mtlsKey !== undefined && options.mtlsKey !== '') {\n present.push('--mtls-key');\n } else {\n absent.push('--mtls-key');\n }\n if (present.length === 0) return undefined;\n if (absent.length > 0) {\n throw new Error(\n `mTLS configuration is incomplete: ${present.join(', ')} set but ${absent.join(', ')} missing. ` +\n 'All three of --mtls-truststore, --mtls-cert, and --mtls-key must be set together to enable mTLS, ' +\n 'or all three left unset for plain HTTP.'\n );\n }\n // All three set — read the PEM materials from disk. Failures surface\n // a clear error naming the offending flag + path before the server\n // starts.\n return readMtlsMaterialsFromDisk({\n truststorePath: options.mtlsTruststore!,\n certPath: options.mtlsCert!,\n keyPath: options.mtlsKey!,\n });\n}\n\n/**\n * Builder for the `start-api` subcommand.\n */\nexport function createLocalStartApiCommand(opts: CreateLocalStartApiCommandOptions = {}): Command {\n setEmbedConfig(opts.embedConfig);\n const startApi = new Command('start-api')\n .description(\n 'Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and AWS_IAM auth (REST v1 `AuthorizationType: AWS_IAM` and Function URL `AuthType: AWS_IAM` — SigV4 signature verification only; IAM policy evaluation is NOT emulated). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.'\n )\n .argument(\n '[targets...]',\n `Optional API subset filter. Pass one or more identifiers to serve exactly that subset (the union; each on its own port). Each accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted in a TTY, a multi-select picker opens with every API pre-selected (Enter serves all, deselect to pick a subset); when omitted in a non-TTY (CI / pipe) every discovered API is served. Mirrors \\`${getEmbedConfig().cliName} invoke\\` / \\`${getEmbedConfig().cliName} run-task\\` target syntax.`\n )\n .addOption(\n new Option('--port <port>', 'HTTP server port (default: auto-allocate)').default('0')\n )\n .addOption(new Option('--host <host>', 'Bind address').default('127.0.0.1'))\n .addOption(new Option('--stack <name>', 'Stack to start (single-stack apps auto-detect)'))\n .addOption(\n new Option(\n '--all-stacks',\n \"Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target subset, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).\"\n ).default(false)\n )\n .addOption(\n new Option('--warm', 'Pre-start one container per Lambda at server boot').default(false)\n )\n .addOption(\n new Option(\n '--per-lambda-concurrency <n>',\n 'Pool size cap per Lambda (default 2, max 4)'\n ).default('2')\n )\n .addOption(new Option('--no-pull', 'Skip docker pull (cached image)'))\n .addOption(\n new Option(\n '--container-host <host>',\n 'IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.'\n ).default('127.0.0.1')\n )\n .addOption(\n new Option(\n '--debug-port-base <port>',\n 'Reserve a contiguous --debug-port range (one per Lambda)'\n )\n )\n .addOption(\n new Option(\n '--env-vars <file>',\n 'JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})'\n )\n )\n .addOption(\n new Option(\n '--assume-role <arn-or-pair>',\n \"Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).\"\n ).argParser((raw, prev: AssumeRoleOption | undefined) => parseAssumeRoleToken(raw, prev))\n )\n .addOption(\n new Option(\n '--watch',\n \"Hot-reload: re-synth + re-discover routes when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). Off by default; the server keeps the previous version serving when synth fails mid-reload.\"\n ).default(false)\n )\n .addOption(\n new Option(\n '--stage <name>',\n \"Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.\"\n )\n )\n .addOption(\n new Option(\n '--api <id>',\n 'DEPRECATED — use the positional <targets...> argument instead. Accepts a SINGLE identifier; for a subset pass multiple positional targets. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.'\n )\n )\n .addOption(\n new Option(\n '--layer-role-arn <arn>',\n 'Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN ' +\n 'entry in Properties.Layers (issue #448). Use only when the dev credentials cannot ' +\n 'read the layer — typically cross-account layers. AWS-published public layers (e.g. ' +\n 'Lambda Powertools) are readable from every account and need no role.'\n )\n )\n .addOption(\n new Option(\n '--from-cfn-stack [cfn-stack-name]',\n 'Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue ' +\n 'in Lambda env vars with the deployed physical IDs / exports. ' +\n 'Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). ' +\n 'Bare form uses the resolved stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. ' +\n 'Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).'\n )\n )\n .addOption(\n new Option(\n '--stack-region <region>',\n 'Region of the state record to read. Used with --from-cfn-stack as the CFn client region.'\n )\n )\n .addOption(\n new Option(\n '--mtls-truststore <path>',\n 'PEM-encoded CA bundle for client-certificate verification (mutual TLS). ' +\n 'When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects ' +\n \"clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced \" +\n 'on the Lambda event under requestContext.identity.clientCert (REST v1) / ' +\n 'requestContext.authentication.clientCert (HTTP API v2). Must be set together with ' +\n '--mtls-cert + --mtls-key; partial flag sets are rejected. ' +\n 'Generate a CA + server + client cert for local dev: ' +\n `openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj \"/CN=${getEmbedConfig().resourceNamePrefix}-ca\" -days 365; ` +\n 'openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj \"/CN=localhost\"; ' +\n 'openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; ' +\n 'openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj \"/CN=client\"; ' +\n 'openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; ' +\n 'curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...'\n )\n )\n .addOption(\n new Option(\n '--mtls-cert <path>',\n 'PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. ' +\n 'Must be set together with --mtls-truststore + --mtls-key.'\n )\n )\n .addOption(\n new Option(\n '--mtls-key <path>',\n 'PEM-encoded server private key matching --mtls-cert. ' +\n 'Must be set together with --mtls-truststore + --mtls-cert.'\n )\n )\n .addOption(\n new Option(\n '--strict-sigv4',\n 'Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically verified ' +\n '(foreign access-key-id — e.g. a federated / Cognito Identity Pool / cross-account ' +\n 'signer — OR no local AWS credentials configured) instead of the default warn-and-pass. ' +\n 'DEFAULT off: cdk-local warn-and-passes unverifiable IAM requests with a placeholder ' +\n 'principalId so local dev exercises app logic without reproducing an auth boundary it ' +\n 'cannot fully emulate. OAC-fronted Function URLs always warn-and-pass regardless.'\n ).default(false)\n )\n .action(\n withErrorHandling(async (targets: string[], options: LocalStartApiOptions) => {\n await localStartApiCommand(targets, options, opts.extraStateProviders);\n })\n );\n\n [...commonOptions(), ...appOptions(), ...contextOptions].forEach((opt) =>\n startApi.addOption(opt)\n );\n startApi.addOption(deprecatedRegionOption);\n\n return startApi;\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { getDockerCmd } from '../utils/docker-cmd.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Phase 3 of #262 (Issue #460) helper — query the docker network IP\n * assigned to a freshly-started container so the Cloud Map registry\n * can publish reachable endpoints for peer discovery.\n *\n * Returns `undefined` when:\n * - The container is not found (docker rm raced us).\n * - The container is not attached to the named network.\n * - The IP is empty (docker hasn't fully wired the network yet —\n * caller should retry-or-skip; the helper deliberately does NOT\n * embed a retry loop to keep the caller in charge of timing).\n *\n * Errors propagate verbatim to the caller (`DockerRunnerError`-style\n * wrapping is the caller's concern; this helper is structurally a\n * simple inspect wrapper).\n */\nexport async function getContainerNetworkIp(\n containerId: string,\n networkName: string\n): Promise<string | undefined> {\n const format = `{{with index .NetworkSettings.Networks \"${networkName}\"}}{{.IPAddress}}{{end}}`;\n try {\n const { stdout } = await execFileAsync(getDockerCmd(), [\n 'inspect',\n '--format',\n format,\n containerId,\n ]);\n const ip = stdout.trim();\n if (!ip) return undefined;\n return ip;\n } catch {\n // Container vanished between docker run and inspect (typical on a\n // failed boot or a parallel SIGTERM); caller treats undefined as\n // \"skip registration\" and the cleanup path handles teardown.\n return undefined;\n }\n}\n\n/**\n * Issue #86 v1 helper — read back the docker-assigned host port a container\n * port was published on. The local ALB front-door publishes each replica's\n * target container port on an ephemeral host port (`-p <host>::<containerPort>`)\n * and round-robins to `127.0.0.1:<hostPort>`; this resolves that host port\n * after the replica boots.\n *\n * Uses a nil-safe Go template (`with index`) so a container that did NOT\n * publish the port yields empty output instead of a template error. Returns\n * `undefined` when the port is unpublished, the container vanished, or the\n * value isn't a parseable port.\n */\nexport async function getPublishedHostPort(\n containerId: string,\n containerPort: number,\n protocol: 'tcp' | 'udp' = 'tcp'\n): Promise<number | undefined> {\n const key = `${containerPort}/${protocol}`;\n const format = `{{with index .NetworkSettings.Ports \"${key}\"}}{{with index . 0}}{{.HostPort}}{{end}}{{end}}`;\n try {\n const { stdout } = await execFileAsync(getDockerCmd(), [\n 'inspect',\n '--format',\n format,\n containerId,\n ]);\n const raw = stdout.trim();\n if (!raw) return undefined;\n const port = parseInt(raw, 10);\n return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : undefined;\n } catch {\n return undefined;\n }\n}\n","import { getLogger } from '../utils/logger.js';\n\n/**\n * Phase 3 of #262 (Issue #460) — in-process Cloud Map / Service Connect\n * service registry consumed by `cdkl start-service` to surface a\n * single shared discovery table across multiple services and their\n * replicas.\n *\n * AWS-side mapping:\n * - `AWS::ServiceDiscovery::PrivateDnsNamespace` (Cloud Map namespace) →\n * `RegisteredNamespace` (string `name`).\n * - `AWS::ServiceDiscovery::Service` (Cloud Map service) registered via\n * `cloudMapOptions` on an ECS Service AND the Service Connect\n * `ClientAliases[].DnsName` (which AWS-side ECS Agent registers in\n * Cloud Map automatically when Service Connect is enabled) → one\n * `RegisteredEndpoint` per replica that becomes routable from peer\n * services in the same docker network.\n *\n * **Scope (v1)** — limited to the surfaces needed for the `--add-host`\n * DNS overlay (per the design's \"DNS-only\" combining-layer table at\n * §4):\n * - A-record style resolution: `{discoveryName}.{namespace}` AND the\n * bare `{discoveryName}` (the ClientAlias short form) resolve to a\n * single IP per replica. Multiple replicas register independently;\n * consumers connect to *one* of them via `--add-host` round-robin —\n * docker uses the *first* matching `--add-host` entry, so multi-\n * replica routing is approximated as \"first-replica-wins\". A full\n * multi-instance DNS rotation (matching AWS's Cloud Map MULTIVALUE\n * RoutingPolicy) is deferred to PR 3a's DNS-sidecar option (§6).\n * - SRV records, Envoy sidecar, HttpNamespace, PublicDnsNamespace:\n * out of scope (rejected upstream in the resolver layer or via the\n * \"deferred to follow-up PR\" pointers).\n *\n * **Process scoping**: the registry is a singleton per `cdk-local\n * start-service` invocation. Multiple cdk-local processes on the same host\n * do NOT share registry state — by design, since they'd be on different\n * docker networks anyway and the design § \"Local docker network shape\"\n * Option A pins \"one docker network per CDK app per CLI invocation\".\n * Tests construct fresh instances via `new CloudMapRegistry()` to avoid\n * test-cross-talk.\n */\n\n/** One reachable replica registered in the discovery table. */\nexport interface RegisteredEndpoint {\n /** Docker IP address inside the per-service network (e.g. `172.17.0.5`). */\n ip: string;\n /** Container port the consumer should connect to. */\n port: number;\n /** Stable identity for unregister: typically `<serviceLogicalId>:<replicaIndex>`. */\n ownerKey: string;\n}\n\n/** A registration handle returned by `register()` for symmetric unregister. */\nexport interface RegistrationHandle {\n /** Fully-qualified key `{namespace}/{discoveryName}` for fast lookup. */\n readonly fqdn: string;\n /** Owner stamp used for `unregister`. */\n readonly ownerKey: string;\n}\n\n/** Internal entry — one per (fqdn, ownerKey) pair. */\ninterface RegistryEntry {\n namespace: string;\n discoveryName: string;\n endpoint: RegisteredEndpoint;\n}\n\n/**\n * Shape returned by `list()` for diagnostic / boot-log surfaces.\n * Aliases (e.g. bare `<discoveryName>` ClientAlias short-form) are\n * surfaced as additional entries; consumers iterate without needing to\n * know which entry is the canonical fqdn vs an alias.\n */\nexport interface RegistryListing {\n namespace: string;\n discoveryName: string;\n endpoints: ReadonlyArray<RegisteredEndpoint>;\n /** True when this row is an alias (bare `<discoveryName>`, no namespace). */\n isAlias: boolean;\n}\n\n/**\n * In-process registry. `register()` is called by the service runner\n * after each replica's main container boot; `unregister()` is called by\n * the shutdown / restart paths. `lookupHosts()` produces the\n * `--add-host` flag list a consumer task's `docker run` injects so\n * `<discoveryName>.<namespace>` and (when registered as an alias)\n * bare `<discoveryName>` both resolve to a registered endpoint inside\n * the consumer container.\n *\n * Concurrency note: docker-run callers are not concurrent for the same\n * replica (the runner boots replicas sequentially), but `lookupHosts()`\n * MAY be called concurrently with `register()` / `unregister()` of an\n * unrelated service. The implementation uses synchronous Map mutations\n * so a stale read returns the previous snapshot — never a partially-\n * mutated one. No async / mutex needed.\n */\nexport class CloudMapRegistry {\n /** Map<fqdn, RegistryEntry[]> — one per replica registered under that fqdn. */\n private readonly byFqdn: Map<string, RegistryEntry[]> = new Map();\n /**\n * Map<alias, fqdn> — secondary index for ClientAlias short forms.\n * Multiple aliases can point at the same fqdn; one alias only points\n * at one fqdn. **First-wins on collision** — consistent with design\n * §O6's namespace-level first-wins rule for `PrivateDnsNamespace`. A\n * collision (two services declaring the same `ClientAlias.DnsName`)\n * emits a `logger.warn` so users can debug \"why does\n * `wget http://api/` reach service B instead of service A\" without\n * silently shadowing the prior binding. Idempotent re-register of\n * the same `(alias, targetFqdn)` pair is a no-op and does NOT warn.\n */\n private readonly aliasIndex: Map<string, string> = new Map();\n\n /**\n * Register a replica's endpoint under `{namespace}/{discoveryName}`.\n *\n * @param namespace Cloud Map namespace name, e.g. `cdkl.local`.\n * An empty string is rejected — Cloud Map requires a\n * named namespace.\n * @param discoveryName Cloud Map service name, e.g. `orders`. Rejected\n * when empty.\n * @param endpoint Reachable endpoint + owner key for symmetric\n * unregister.\n * @returns A handle the caller stores for later `unregister(handle)`.\n */\n register(\n namespace: string,\n discoveryName: string,\n endpoint: RegisteredEndpoint\n ): RegistrationHandle {\n if (!namespace) {\n throw new Error('CloudMapRegistry.register: namespace must be a non-empty string.');\n }\n if (!discoveryName) {\n throw new Error('CloudMapRegistry.register: discoveryName must be a non-empty string.');\n }\n const fqdn = `${discoveryName}.${namespace}`;\n const entries = this.byFqdn.get(fqdn) ?? [];\n // Replace any prior entry with the same ownerKey (idempotent\n // re-register after a replica restart).\n const filtered = entries.filter((e) => e.endpoint.ownerKey !== endpoint.ownerKey);\n filtered.push({ namespace, discoveryName, endpoint });\n this.byFqdn.set(fqdn, filtered);\n return { fqdn, ownerKey: endpoint.ownerKey };\n }\n\n /**\n * Register a bare-name alias (`<discoveryName>` without the namespace\n * suffix). Cloud Map / Service Connect does NOT auto-create such\n * aliases — they're populated by `ClientAliases[].DnsName` entries in\n * the consumer service's `ServiceConnectConfiguration`. Aliases are\n * scoped per-CLI-invocation and **first-wins on collision** —\n * consistent with design §O6's namespace-level first-wins rule. The\n * first registration sticks; later attempts to bind the same alias\n * to a different fqdn are ignored and a `logger.warn` is emitted so\n * users can debug \"why does `wget http://api/` reach service B\n * instead of service A\". Re-registering the same `(alias,\n * targetFqdn)` pair is idempotent and does NOT warn.\n *\n * @param alias The bare discovery name (e.g. `orders` for an alias to\n * `orders.cdkl.local`).\n * @param targetFqdn The full `{discoveryName}.{namespace}` the alias\n * resolves to.\n */\n registerAlias(alias: string, targetFqdn: string): void {\n if (!alias) {\n throw new Error('CloudMapRegistry.registerAlias: alias must be a non-empty string.');\n }\n if (!targetFqdn) {\n throw new Error('CloudMapRegistry.registerAlias: targetFqdn must be a non-empty string.');\n }\n const existing = this.aliasIndex.get(alias);\n if (existing !== undefined) {\n // Idempotent re-register from the same source — no-op, no warn.\n if (existing === targetFqdn) return;\n // Cross-source collision: first-wins. Keep the existing binding\n // and surface a warn so users can debug surprising routing.\n getLogger()\n .child('cloud-map-registry')\n .warn(\n `ClientAlias DnsName collision: '${alias}' was already mapped to '${existing}'; ` +\n `keeping first-wins binding and ignoring new mapping to '${targetFqdn}'. ` +\n 'Likely cause: two Service Connect services declared the same ClientAlias.DnsName.'\n );\n return;\n }\n this.aliasIndex.set(alias, targetFqdn);\n }\n\n /**\n * Remove a single endpoint registered under the supplied handle.\n * Idempotent — unknown handles return false without throwing.\n */\n unregister(handle: RegistrationHandle): boolean {\n const entries = this.byFqdn.get(handle.fqdn);\n if (!entries) return false;\n const filtered = entries.filter((e) => e.endpoint.ownerKey !== handle.ownerKey);\n if (filtered.length === entries.length) return false;\n if (filtered.length === 0) this.byFqdn.delete(handle.fqdn);\n else this.byFqdn.set(handle.fqdn, filtered);\n return true;\n }\n\n /**\n * Drop every registration with the supplied owner key (e.g. teardown\n * of every replica of a service). Used by the service controller's\n * shutdown path; complementary to per-replica `unregister`.\n */\n unregisterByOwner(ownerKeyPrefix: string): number {\n let removed = 0;\n for (const [fqdn, entries] of [...this.byFqdn.entries()]) {\n const filtered = entries.filter((e) => !e.endpoint.ownerKey.startsWith(ownerKeyPrefix));\n removed += entries.length - filtered.length;\n if (filtered.length === 0) this.byFqdn.delete(fqdn);\n else this.byFqdn.set(fqdn, filtered);\n }\n return removed;\n }\n\n /**\n * Look up every endpoint registered under `{discoveryName}.{namespace}`.\n * Returns `undefined` when no endpoint exists (which is the consumer\n * runner's signal to log a warn — the user likely forgot to start the\n * producer). Returns the underlying array verbatim so callers cannot\n * accidentally mutate registry state — call `.slice()` if you need a\n * detachable copy.\n */\n lookup(namespace: string, discoveryName: string): ReadonlyArray<RegisteredEndpoint> | undefined {\n const fqdn = `${discoveryName}.${namespace}`;\n const entries = this.byFqdn.get(fqdn);\n if (!entries || entries.length === 0) return undefined;\n return entries.map((e) => e.endpoint);\n }\n\n /**\n * Resolve a bare alias to its target endpoints. Returns the same\n * shape as `lookup` for the alias's target fqdn, or `undefined` when\n * no such alias exists (which is distinct from \"alias known but\n * target has no live replica\" — caller may want different warns).\n */\n lookupAlias(alias: string): ReadonlyArray<RegisteredEndpoint> | undefined {\n const fqdn = this.aliasIndex.get(alias);\n if (!fqdn) return undefined;\n const entries = this.byFqdn.get(fqdn);\n if (!entries || entries.length === 0) return undefined;\n return entries.map((e) => e.endpoint);\n }\n\n /**\n * Build the `--add-host` flag list for a consumer container's\n * `docker run`. Returns each unique `(hostname, ip)` pair as a flat\n * `['--add-host', 'name:ip', ...]` array consumable verbatim by the\n * docker-runner. Includes both the fqdn AND every alias mapped to it.\n *\n * Multiple replicas per fqdn cannot be expressed as multiple\n * `--add-host` entries with the same name (docker's resolver takes\n * the *last* entry on duplicate keys per `getent hosts` semantics),\n * so this returns the **first** registered endpoint per fqdn /\n * alias. Multi-instance round-robin via the static `--add-host`\n * shape is structurally impossible; a true rotation requires the\n * DNS-sidecar option (deferred). Documented as a v1 limitation.\n */\n buildAddHostFlags(excludeOwnerKeyPrefix?: string): string[] {\n const flags: string[] = [];\n const seen = new Set<string>();\n // fqdn entries first so an alias collision (same name) doesn't\n // shadow a fully-qualified one — docker uses the first matching\n // entry by default and the fqdn variant should be the canonical.\n for (const [fqdn, entries] of this.byFqdn.entries()) {\n const candidate = entries.find(\n (e) => !excludeOwnerKeyPrefix || !e.endpoint.ownerKey.startsWith(excludeOwnerKeyPrefix)\n );\n if (!candidate) continue;\n if (seen.has(fqdn)) continue;\n flags.push('--add-host', `${fqdn}:${candidate.endpoint.ip}`);\n seen.add(fqdn);\n }\n for (const [alias, targetFqdn] of this.aliasIndex.entries()) {\n if (seen.has(alias)) continue;\n const entries = this.byFqdn.get(targetFqdn);\n if (!entries || entries.length === 0) continue;\n const candidate = entries.find(\n (e) => !excludeOwnerKeyPrefix || !e.endpoint.ownerKey.startsWith(excludeOwnerKeyPrefix)\n );\n if (!candidate) continue;\n flags.push('--add-host', `${alias}:${candidate.endpoint.ip}`);\n seen.add(alias);\n }\n return flags;\n }\n\n /**\n * Diagnostic snapshot used by the boot banner / test assertions.\n * Stable iteration order (insertion-order is preserved by JS Maps).\n */\n list(): ReadonlyArray<RegistryListing> {\n const out: RegistryListing[] = [];\n for (const [, entries] of this.byFqdn.entries()) {\n if (entries.length === 0) continue;\n const first = entries[0]!;\n out.push({\n namespace: first.namespace,\n discoveryName: first.discoveryName,\n endpoints: entries.map((e) => e.endpoint),\n isAlias: false,\n });\n }\n for (const [alias, fqdn] of this.aliasIndex.entries()) {\n const entries = this.byFqdn.get(fqdn);\n if (!entries || entries.length === 0) continue;\n out.push({\n namespace: '', // aliases are namespace-less by definition\n discoveryName: alias,\n endpoints: entries.map((e) => e.endpoint),\n isAlias: true,\n });\n }\n return out;\n }\n\n /** True when no endpoint is registered. Used by the runner to short-circuit. */\n isEmpty(): boolean {\n return this.byFqdn.size === 0;\n }\n}\n","import type { StackInfo } from '../synthesis/assembly-reader.js';\nimport { EcsTaskResolutionError } from './ecs-task-resolver.js';\nimport { getEmbedConfig } from './embed-config.js';\n\n/**\n * Phase 3 of #262 (Issue #460) — `AWS::ServiceDiscovery::*` template\n * resolver. Walks the synthesized template once and produces a\n * stack-scoped index the service runner consults at boot to know:\n *\n * - Which `PrivateDnsNamespace` logical ids are valid registration\n * targets, and what their `Name` (= the namespace string used in\n * `<discoveryName>.<namespace>` DNS lookups) is.\n * - Which `AWS::ServiceDiscovery::Service` entries the user\n * templated, what namespace they belong to, and what discovery\n * name they register under. (`cdkl start-service` then\n * registers each replica of the ECS Service that has this as a\n * `ServiceRegistry` target into the matching namespace + discovery\n * name.)\n *\n * **Non-private namespaces hard-reject** at the resolver layer per the\n * design doc's §2 \"Non-goals\" table — `PublicDnsNamespace` and\n * `HttpNamespace` are out of scope for local emulation. The resolver\n * doesn't actually surface those types (they're filtered upstream), but\n * each ECS Service's `ServiceConnectConfiguration.Namespace` value (a\n * literal string `Name`, NOT a Ref / ARN, per CDK 2.x synth shape\n * verified 2026-05-22) is matched against the resolved\n * `PrivateDnsNamespace` set; an unmatched namespace surfaces as an\n * explicit error rather than silently routing to nothing.\n */\nexport interface ResolvedCloudMapNamespace {\n /** AWS::ServiceDiscovery::PrivateDnsNamespace logical id. */\n logicalId: string;\n /** Namespace name as it would be looked up in DNS, e.g. `cdkl.local`. */\n name: string;\n}\n\nexport interface ResolvedCloudMapService {\n /** AWS::ServiceDiscovery::Service logical id. */\n logicalId: string;\n /** Logical id of the parent namespace. */\n namespaceLogicalId: string;\n /** Resolved namespace name (denormalized for convenience). */\n namespaceName: string;\n /** Service name, e.g. `orders`. */\n name: string;\n /**\n * DNS record types declared by the user. cdkl v1 emits the same\n * `--add-host` mapping regardless of A vs SRV — both round-trip to\n * a single IP per consumer in the docker overlay (SRV record port\n * routing requires the DNS-sidecar option deferred from §6). The\n * field is parsed and surfaced so a future SRV-aware DNS sidecar\n * can consume it without re-parsing the template.\n */\n dnsRecords: ReadonlyArray<{ type: 'A' | 'SRV'; ttlSeconds: number }>;\n}\n\n/**\n * Pure-functional walk of one stack's template. Returns the resolved\n * namespace + service index for that stack. Cross-stack references\n * (Cloud Map Service in stack A referencing a namespace in stack B\n * via `Fn::ImportValue`) are NOT supported in v1 and surface as a\n * resolver error.\n */\nexport interface CloudMapIndex {\n /** All resolved private DNS namespaces, keyed by logical id. */\n namespacesByLogicalId: Map<string, ResolvedCloudMapNamespace>;\n /** Secondary index by namespace name (for `ServiceConnectConfiguration.Namespace` literal lookups). */\n namespacesByName: Map<string, ResolvedCloudMapNamespace>;\n /** All resolved cloud-map services, keyed by logical id. */\n servicesByLogicalId: Map<string, ResolvedCloudMapService>;\n /**\n * Non-fatal warnings (e.g. an unsupported namespace type, a service\n * that references a namespace not in this stack). Surfaced at boot\n * so users learn what's not being emulated.\n */\n warnings: string[];\n}\n\n/**\n * Build the `CloudMapIndex` for one stack's template. Empty index when\n * the stack declares no `AWS::ServiceDiscovery::*` resources.\n *\n * Hard-reject errors (throw `EcsTaskResolutionError`):\n * - `AWS::ServiceDiscovery::PublicDnsNamespace` — defeats \"local\" semantics.\n * - `AWS::ServiceDiscovery::HttpNamespace` — DiscoverInstances-only,\n * no DNS, would require shimming the AWS SDK inside every container.\n * - An `AWS::ServiceDiscovery::Service` with a `NamespaceId` that\n * doesn't resolve to a same-stack `PrivateDnsNamespace`.\n */\nexport function buildCloudMapIndex(stack: StackInfo): CloudMapIndex {\n const namespacesByLogicalId = new Map<string, ResolvedCloudMapNamespace>();\n const namespacesByName = new Map<string, ResolvedCloudMapNamespace>();\n const servicesByLogicalId = new Map<string, ResolvedCloudMapService>();\n const warnings: string[] = [];\n const resources = stack.template.Resources ?? {};\n\n // PASS 1: namespaces first so service lookups have something to match.\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type === 'AWS::ServiceDiscovery::PublicDnsNamespace') {\n throw new EcsTaskResolutionError(\n `Stack ${stack.stackName}: AWS::ServiceDiscovery::PublicDnsNamespace '${logicalId}' ` +\n 'is not supported by local emulation — public DNS defeats the \"local\" point. ' +\n `Use a PrivateDnsNamespace for ${getEmbedConfig().cliName} start-service.`\n );\n }\n if (resource.Type === 'AWS::ServiceDiscovery::HttpNamespace') {\n throw new EcsTaskResolutionError(\n `Stack ${stack.stackName}: AWS::ServiceDiscovery::HttpNamespace '${logicalId}' is not ` +\n 'supported by local emulation — HttpNamespace uses the AWS Cloud Map ' +\n 'DiscoverInstances API directly (no DNS), which would require shimming the AWS SDK ' +\n 'inside every container. Use a PrivateDnsNamespace + DnsConfig instead.'\n );\n }\n if (resource.Type !== 'AWS::ServiceDiscovery::PrivateDnsNamespace') continue;\n\n const props = (resource.Properties ?? {}) as Record<string, unknown>;\n const name = typeof props['Name'] === 'string' ? props['Name'] : undefined;\n if (!name) {\n throw new EcsTaskResolutionError(\n `Stack ${stack.stackName}: PrivateDnsNamespace '${logicalId}' has no literal Name ` +\n 'property. Intrinsic-valued names are not supported (cross-stack / dynamic ' +\n 'namespace names require deploy-state resolution which is out of scope for v1).'\n );\n }\n const entry: ResolvedCloudMapNamespace = { logicalId, name };\n namespacesByLogicalId.set(logicalId, entry);\n if (namespacesByName.has(name)) {\n // Two namespaces with the same name in the same stack. CDK's\n // own `cluster.addDefaultCloudMapNamespace(...)` AND an\n // explicit `new cloudmap.PrivateDnsNamespace(...)` can collide\n // on the same name (verified via real synth). Keep the first;\n // surface a warn so users notice.\n warnings.push(\n `Stack ${stack.stackName}: two PrivateDnsNamespace resources share Name='${name}' ` +\n `('${namespacesByName.get(name)!.logicalId}' and '${logicalId}'). ` +\n 'Local emulation routes registrations to the first; the second will silently shadow.'\n );\n } else {\n namespacesByName.set(name, entry);\n }\n }\n\n // PASS 2: services — needs the namespace index to resolve NamespaceId.\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::ServiceDiscovery::Service') continue;\n const props = (resource.Properties ?? {}) as Record<string, unknown>;\n\n const namespaceLogicalId = resolveNamespaceIdRef(\n props['NamespaceId'] ??\n (props['DnsConfig'] as Record<string, unknown> | undefined)?.['NamespaceId'],\n stack.stackName,\n logicalId\n );\n\n const ns = namespacesByLogicalId.get(namespaceLogicalId);\n if (!ns) {\n throw new EcsTaskResolutionError(\n `Stack ${stack.stackName}: AWS::ServiceDiscovery::Service '${logicalId}' references ` +\n `NamespaceId '${namespaceLogicalId}' but no PrivateDnsNamespace with that logical id ` +\n 'exists in this stack. Cross-stack Cloud Map namespaces are not supported in v1.'\n );\n }\n\n const name = typeof props['Name'] === 'string' ? props['Name'] : undefined;\n if (!name) {\n throw new EcsTaskResolutionError(\n `Stack ${stack.stackName}: AWS::ServiceDiscovery::Service '${logicalId}' has no literal ` +\n 'Name property. Intrinsic-valued names are not supported in v1.'\n );\n }\n\n const dnsRecords = extractDnsRecords(props);\n\n servicesByLogicalId.set(logicalId, {\n logicalId,\n namespaceLogicalId,\n namespaceName: ns.name,\n name,\n dnsRecords,\n });\n }\n\n return { namespacesByLogicalId, namespacesByName, servicesByLogicalId, warnings };\n}\n\n/**\n * Resolve `NamespaceId` to the parent's logical id. CDK 2.x synthesizes\n * this as `{Fn::GetAtt: ['<NsLogicalId>', 'Id']}` (verified via real\n * `cdk synth` on 2026-05-22). `Ref` is also accepted defensively\n * (returns the namespace's physical id, but inside one synth template\n * the Ref target IS the logical id we want).\n *\n * Cross-stack / intrinsic shapes that we cannot resolve at synth time\n * are hard-rejected — cdk-local would otherwise silently route to no\n * namespace and the consumer would get an unhelpful \"DNS lookup failed\"\n * at runtime.\n */\nfunction resolveNamespaceIdRef(raw: unknown, stackName: string, serviceLogicalId: string): string {\n if (typeof raw === 'string') {\n // Defensive: literal string id. Caller will match against the\n // namespace index and the literal is unlikely to match unless the\n // user pre-resolved it manually — but accept rather than reject.\n return raw;\n }\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n const obj = raw as Record<string, unknown>;\n if (typeof obj['Ref'] === 'string') return obj['Ref'];\n const getAtt = obj['Fn::GetAtt'];\n if (Array.isArray(getAtt) && typeof getAtt[0] === 'string') {\n // `Fn::GetAtt: ['<NsLogicalId>', 'Id']` is the CDK-canonical shape.\n // We surface the logical id regardless of which attribute was\n // requested — the namespace index keys on logical id.\n return getAtt[0];\n }\n }\n throw new EcsTaskResolutionError(\n `Stack ${stackName}: AWS::ServiceDiscovery::Service '${serviceLogicalId}' has an ` +\n `unsupported NamespaceId reference shape: ${JSON.stringify(raw)}. Accepted shapes are ` +\n \"{Fn::GetAtt: [<NsLogicalId>, 'Id']} or {Ref: <NsLogicalId>} pointing at a same-stack \" +\n 'PrivateDnsNamespace.'\n );\n}\n\nfunction extractDnsRecords(\n serviceProps: Record<string, unknown>\n): ReadonlyArray<{ type: 'A' | 'SRV'; ttlSeconds: number }> {\n const dnsConfig = serviceProps['DnsConfig'];\n if (!dnsConfig || typeof dnsConfig !== 'object') return [];\n const records = (dnsConfig as Record<string, unknown>)['DnsRecords'];\n if (!Array.isArray(records)) return [];\n const out: { type: 'A' | 'SRV'; ttlSeconds: number }[] = [];\n for (const r of records) {\n if (!r || typeof r !== 'object') continue;\n const obj = r as Record<string, unknown>;\n const type = obj['Type'];\n if (type !== 'A' && type !== 'SRV') continue; // AAAA / CNAME skipped per §6.\n const ttl = obj['TTL'];\n const ttlSeconds =\n typeof ttl === 'number' && Number.isFinite(ttl) && ttl >= 0\n ? Math.floor(ttl)\n : typeof ttl === 'string' && /^\\d+$/.test(ttl)\n ? parseInt(ttl, 10)\n : 60;\n out.push({ type, ttlSeconds });\n }\n return out;\n}\n"],"mappings":"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"}