@go-to-k/cdkd 0.101.1 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"deploy-engine-BzsWm3DG.js","names":["err","execFileAsync","DescribeImagesCommand","err"],"sources":["../src/provisioning/resource-name.ts","../src/utils/live-renderer.ts","../src/utils/stack-context.ts","../src/utils/logger.ts","../src/utils/error-handler.ts","../src/utils/aws-region-resolver.ts","../src/synthesis/app-executor.ts","../src/types/assembly.ts","../src/synthesis/assembly-reader.ts","../src/synthesis/context-store.ts","../src/synthesis/context-providers/az-provider.ts","../src/synthesis/context-providers/ssm-provider.ts","../src/synthesis/context-providers/hosted-zone-provider.ts","../src/synthesis/context-providers/vpc-provider.ts","../src/synthesis/context-providers/cc-api-provider.ts","../src/synthesis/context-providers/ami-provider.ts","../src/synthesis/context-providers/security-group-provider.ts","../src/synthesis/context-providers/load-balancer-provider.ts","../src/synthesis/context-providers/key-provider.ts","../src/synthesis/context-providers/index.ts","../src/cli/config-loader.ts","../src/synthesis/synthesizer.ts","../src/assets/file-asset-publisher.ts","../src/assets/docker-build.ts","../src/assets/docker-asset-publisher.ts","../src/deployment/work-graph.ts","../src/utils/stringify.ts","../src/assets/asset-publisher.ts","../src/types/state.ts","../src/state/s3-state-backend.ts","../src/state/lock-manager.ts","../src/analyzer/template-parser.ts","../src/analyzer/lambda-vpc-deps.ts","../src/analyzer/cdk-defensive-deps.ts","../src/analyzer/dag-builder.ts","../src/analyzer/replacement-rules.ts","../src/analyzer/diff-calculator.ts","../src/deployment/intrinsic-function-resolver.ts","../src/provisioning/json-patch-generator.ts","../src/provisioning/region-check.ts","../src/provisioning/cloud-control-provider.ts","../src/provisioning/providers/custom-resource-provider.ts","../src/provisioning/provider-registry.ts","../src/provisioning/import-helpers.ts","../src/provisioning/providers/iam-role-provider.ts","../src/deployment/dag-executor.ts","../src/analyzer/implicit-delete-deps.ts","../src/deployment/retryable-errors.ts","../src/deployment/retry.ts","../src/deployment/resource-deadline.ts","../src/deployment/deploy-engine.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { createHash } from 'node:crypto';\n\n/**\n * Per-async-context stack name. Resource-name generation reads this so that\n * concurrent deploys (`cdkd deploy --all` runs stacks in parallel up to\n * `--stack-concurrency`) don't fight over a single shared variable.\n *\n * History: this was `let currentStackName: string | undefined` until\n * 2026-05-01. Two parallel `deploy()` calls would each call\n * `setCurrentStackName(...)` and the second would overwrite the first;\n * any IAM Role / SQS Queue / etc. created by the first stack while the\n * second was active would get the second stack's prefix in its physical\n * name, then the second stack's own create attempt for the same logical\n * id would collide (\"Role with name X already exists\"). Switching to\n * `AsyncLocalStorage` scopes the value to each deploy's async chain.\n */\nconst stackNameStore = new AsyncLocalStorage<string>();\n\n/**\n * Run `fn` with `stackName` set as the stack name visible to\n * `generateResourceName` for the duration of the callback (and any\n * `await`s inside). Concurrent invocations each get an independent scope\n * — this is the safe API for parallel deploys.\n */\nexport function withStackName<T>(stackName: string, fn: () => Promise<T>): Promise<T>;\nexport function withStackName<T>(stackName: string, fn: () => T): T;\nexport function withStackName<T>(stackName: string, fn: () => T | Promise<T>): T | Promise<T> {\n return stackNameStore.run(stackName, fn);\n}\n\n/**\n * Read the current async context's stack name, if any.\n *\n * Returns `undefined` outside any `withStackName` / `setCurrentStackName`\n * scope. Used by the live renderer to scope per-stack in-flight task\n * entries so concurrent deploys don't clobber each other's tasks (same\n * `logicalId` in two stacks would collide on the singleton renderer's\n * task Map without this).\n */\nexport function getCurrentStackName(): string | undefined {\n return stackNameStore.getStore();\n}\n\n/**\n * Set the current async context's stack name.\n *\n * @deprecated Use {@link withStackName} for new code — it makes the scope\n * obvious at the call site. This setter now uses\n * `AsyncLocalStorage.enterWith` so it remains safe under\n * `--stack-concurrency > 1` (each `deploy()` call has its own async\n * resource, so the value does NOT leak across siblings), but\n * `withStackName` is structurally clearer.\n */\nexport function setCurrentStackName(stackName: string): void {\n stackNameStore.enterWith(stackName);\n}\n\n/**\n * Per-async-context \"skip the stack-name prefix on user-supplied physical\n * names\" flag. Read by `generateResourceName` when its caller passes\n * `userSupplied: true`; auto-generated-name paths\n * (`generateResourceName(logicalId, ...)`) ignore this flag.\n *\n * Scoped via AsyncLocalStorage so that `--stack-concurrency > 1` runs\n * cannot cross-contaminate — each deploy's body is wrapped in its own\n * `withSkipPrefix(...)` scope (the deploy CLI plumbs the resolved\n * `--no-prefix-user-supplied-names` value through here). Default\n * `false` preserves pre-PR behavior when the flag is not set.\n */\nconst skipPrefixStore = new AsyncLocalStorage<boolean>();\n\n/**\n * Run `fn` with the \"skip prefix on user-supplied names\" flag set to\n * `skip`. Mirrors {@link withStackName} — concurrent invocations each\n * get an independent scope so parallel deploys do not fight over a\n * single shared variable.\n *\n * Wrap this around `withStackName(...)` (innermost is `fn`) in the\n * deploy CLI: `withSkipPrefix(flag, () => withStackName(name, body))`.\n * Order does not matter — the two stores are independent — but\n * consistent ordering keeps the call sites readable.\n */\nexport function withSkipPrefix<T>(skip: boolean, fn: () => Promise<T>): Promise<T>;\nexport function withSkipPrefix<T>(skip: boolean, fn: () => T): T;\nexport function withSkipPrefix<T>(skip: boolean, fn: () => T | Promise<T>): T | Promise<T> {\n return skipPrefixStore.run(skip, fn);\n}\n\n/**\n * Read the current async context's skip-prefix flag. Defaults to\n * `false` when no `withSkipPrefix` scope is active.\n *\n * Public for unit tests; `generateResourceName` consumes this\n * internally.\n */\nexport function getCurrentSkipPrefix(): boolean {\n return skipPrefixStore.getStore() ?? false;\n}\n\n/**\n * Resource types whose pre-PR #297 code path ran user-supplied\n * physical names through `generateResourceName` (= got the stack-name\n * prefix). These are the only types affected by\n * `--no-prefix-user-supplied-names`; flipping the flag against an\n * existing stack proposes REPLACEMENT on every state resource of\n * one of these types whose `physicalId` is still prefixed.\n *\n * Pattern A providers (Lambda, S3, SNS, SQS, DynamoDB, Logs LogGroup,\n * Events Rule, etc.) historically short-circuited user-supplied names\n * **out** of `generateResourceName` entirely — those types never got\n * the prefix regardless of the flag, so they are NOT included here.\n *\n * Used by the deploy-time pre-flight migration check in\n * `src/cli/commands/prefix-migration-check.ts`. Keep in sync with the\n * Pattern B provider call sites that consume\n * `generateResourceNameWithFallback(...)`.\n */\nexport const PATTERN_B_RESOURCE_TYPES: readonly string[] = [\n 'AWS::IAM::Role',\n 'AWS::IAM::User',\n 'AWS::IAM::Group',\n 'AWS::IAM::InstanceProfile',\n 'AWS::ElasticLoadBalancingV2::LoadBalancer',\n 'AWS::ElasticLoadBalancingV2::TargetGroup',\n] as const;\n\n/**\n * For each Pattern B resource type, the CFn template `Properties` field\n * the user sets to supply a physical name (`new iam.Role(this, 'X',\n * { roleName: 'my-role' })` → `Properties.RoleName: 'my-role'`).\n *\n * Used by the prefix-migration check to distinguish user-supplied\n * physical names (which the v0.94.0 default flip would actually\n * REPLACE on next deploy) from auto-generated logical-id-fallback\n * names (which keep the prefix in BOTH old and new default — no\n * REPLACE pending). Without this discriminator, the migration\n * check naively flags every prefix-style physicalId regardless of\n * its origin, surfacing a false-positive WARNING on auto-generated\n * names that won't actually be touched.\n *\n * Keep in sync with `PATTERN_B_RESOURCE_TYPES` and with each\n * provider's `Properties[<NameField>]` lookup in\n * `src/provisioning/providers/`.\n */\nexport const PATTERN_B_NAME_PROPERTIES: Readonly<Record<string, string>> = {\n 'AWS::IAM::Role': 'RoleName',\n 'AWS::IAM::User': 'UserName',\n 'AWS::IAM::Group': 'GroupName',\n 'AWS::IAM::InstanceProfile': 'InstanceProfileName',\n 'AWS::ElasticLoadBalancingV2::LoadBalancer': 'Name',\n 'AWS::ElasticLoadBalancingV2::TargetGroup': 'Name',\n};\n\n/**\n * Options for generating a resource name.\n */\nexport interface ResourceNameOptions {\n /** Maximum length for the name (e.g., 32 for ALB/TG, 64 for IAM, 63 for S3) */\n maxLength: number;\n /** Whether to force lowercase (e.g., S3 buckets) */\n lowercase?: boolean;\n /** Allowed character regex pattern. Characters not matching will be removed.\n * Default: /[^a-zA-Z0-9-]/ (alphanumeric + hyphen) */\n allowedPattern?: RegExp;\n /**\n * `true` when the caller is passing a name the user explicitly\n * declared in their CDK code (e.g. `new iam.Role(this, 'X', {\n * roleName: 'my-role' })`). `false` (default) when the caller is\n * passing the logical-id fallback or any other cdkd-generated value.\n *\n * Combined with the per-deploy `withSkipPrefix(true)` flag, a\n * `userSupplied: true` call skips the stack-name prefix and returns\n * the user's declared name verbatim (after the same sanitize /\n * truncate pipeline). When `userSupplied` is `false` OR\n * `withSkipPrefix` is unset / `false`, the stack-name prefix is\n * applied (pre-PR behavior).\n *\n * This split is load-bearing: cdkd's stack-scoping concern (prefix\n * for cross-stack uniqueness on auto-generated names) must stay\n * coupled to the auto-generated path, NOT to user-declared names —\n * those belong to the user.\n */\n userSupplied?: boolean;\n}\n\n/**\n * Generate a unique resource name from the logical ID.\n *\n * Generates names in CloudFormation-compatible format:\n * `{StackName}-{LogicalId}-{Hash}` (truncated to maxLength).\n *\n * @param name The raw name (from properties or logicalId fallback)\n * @param options Length and character constraints\n * @returns A sanitized, truncated name that fits the constraints\n */\nexport function generateResourceName(name: string, options: ResourceNameOptions): string {\n const {\n maxLength,\n lowercase = false,\n allowedPattern = /[^a-zA-Z0-9-]/g,\n userSupplied = false,\n } = options;\n\n // Include stack name for uniqueness (like CloudFormation does).\n //\n // The prefix is suppressed when the caller marked the name as\n // user-supplied AND the per-deploy `withSkipPrefix(true)` flag is\n // active — the user owns that name and cdkd should not rewrite it.\n // Every other path (logical-id fallback, no withSkipPrefix scope,\n // flag set to false) keeps the prefix for cross-stack uniqueness.\n const currentStackName = stackNameStore.getStore();\n const shouldPrefix = currentStackName && !(userSupplied && getCurrentSkipPrefix());\n const fullName = shouldPrefix ? `${currentStackName}-${name}` : name;\n\n // Apply lowercase BEFORE pattern matching (so A-Z aren't removed by /[^a-z0-9.-]/)\n let sanitized = lowercase ? fullName.toLowerCase() : fullName;\n sanitized = sanitized.replace(allowedPattern, '-');\n\n // Collapse consecutive hyphens and remove leading/trailing\n sanitized = sanitized.replace(/-{2,}/g, '-').replace(/^-+|-+$/g, '');\n\n if (sanitized.length <= maxLength) {\n return sanitized;\n }\n\n // Truncate with hash suffix for uniqueness\n const hash = createHash('sha256').update(fullName).digest('hex').substring(0, 8);\n const maxPrefixLength = maxLength - hash.length - 1; // -1 for separator\n const prefix = sanitized.substring(0, maxPrefixLength).replace(/-+$/, '');\n\n return `${prefix}-${hash}`;\n}\n\n/**\n * Generate a resource name from a user-declared physical name OR\n * fall back to the logical id.\n *\n * Wraps {@link generateResourceName} to express the Pattern B call-site\n * shape (`generateResourceName((properties['Name'] as string | undefined)\n * || logicalId, opts)`) as a single typed helper. The user-supplied\n * branch passes `userSupplied: true`, which makes the per-deploy\n * `withSkipPrefix(true)` flag drop the stack-name prefix on that name.\n * The fallback (logical-id) branch is `userSupplied: false` and keeps\n * the prefix regardless of the flag — auto-generated names rely on\n * the prefix for cross-stack uniqueness.\n *\n * Use at every Pattern B provider call site (currently IAM Role, IAM\n * User, IAM Group, IAM InstanceProfile, ELBv2 LoadBalancer, ELBv2\n * TargetGroup) so the `--no-prefix-user-supplied-names` flag controls\n * those types consistently. Pattern A providers (Lambda, S3, SNS,\n * SQS, DynamoDB, etc.) do NOT need this helper — they already\n * short-circuit the user-supplied name out of the\n * `generateResourceName` call entirely, so the prefix is never\n * applied to user-supplied names regardless of the flag.\n */\nexport function generateResourceNameWithFallback(\n userSuppliedName: string | undefined,\n logicalId: string,\n options: Omit<ResourceNameOptions, 'userSupplied'>\n): string {\n if (userSuppliedName !== undefined && userSuppliedName !== '') {\n return generateResourceName(userSuppliedName, { ...options, userSupplied: true });\n }\n return generateResourceName(logicalId, { ...options, userSupplied: false });\n}\n\n/**\n * Default name generation rules for CC API fallback.\n *\n * When an SDK provider falls back to CC API, the resource may need a\n * default name that the SDK provider would have generated. This map\n * defines the name property and generation options for each resource type.\n *\n * Format: resourceType → { nameProperty, options, postProcess? }\n */\nconst FALLBACK_NAME_RULES: Record<\n string,\n {\n nameProperty: string;\n options: ResourceNameOptions;\n }\n> = {\n 'AWS::S3::Bucket': { nameProperty: 'BucketName', options: { maxLength: 63, lowercase: true } },\n 'AWS::SQS::Queue': { nameProperty: 'QueueName', options: { maxLength: 80 } },\n 'AWS::SNS::Topic': { nameProperty: 'TopicName', options: { maxLength: 256 } },\n 'AWS::Lambda::Function': { nameProperty: 'FunctionName', options: { maxLength: 64 } },\n 'AWS::Lambda::LayerVersion': { nameProperty: 'LayerName', options: { maxLength: 64 } },\n 'AWS::IAM::Role': { nameProperty: 'RoleName', options: { maxLength: 64 } },\n 'AWS::IAM::Policy': { nameProperty: 'PolicyName', options: { maxLength: 64 } },\n 'AWS::IAM::User': { nameProperty: 'UserName', options: { maxLength: 64 } },\n 'AWS::IAM::Group': { nameProperty: 'GroupName', options: { maxLength: 128 } },\n 'AWS::IAM::InstanceProfile': {\n nameProperty: 'InstanceProfileName',\n options: { maxLength: 128 },\n },\n 'AWS::DynamoDB::Table': { nameProperty: 'TableName', options: { maxLength: 255 } },\n 'AWS::ECR::Repository': {\n nameProperty: 'RepositoryName',\n options: { maxLength: 256, lowercase: true },\n },\n 'AWS::ECS::Cluster': { nameProperty: 'ClusterName', options: { maxLength: 255 } },\n 'AWS::ECS::Service': { nameProperty: 'ServiceName', options: { maxLength: 255 } },\n 'AWS::Logs::LogGroup': { nameProperty: 'LogGroupName', options: { maxLength: 512 } },\n 'AWS::CloudWatch::Alarm': { nameProperty: 'AlarmName', options: { maxLength: 256 } },\n 'AWS::Events::Rule': { nameProperty: 'Name', options: { maxLength: 64 } },\n 'AWS::Events::EventBus': { nameProperty: 'Name', options: { maxLength: 256 } },\n 'AWS::Kinesis::Stream': { nameProperty: 'Name', options: { maxLength: 128 } },\n 'AWS::StepFunctions::StateMachine': {\n nameProperty: 'StateMachineName',\n options: { maxLength: 80 },\n },\n 'AWS::SecretsManager::Secret': {\n nameProperty: 'Name',\n options: { maxLength: 512, allowedPattern: /[^a-zA-Z0-9-/_]/g },\n },\n 'AWS::SSM::Parameter': { nameProperty: 'Name', options: { maxLength: 2048 } },\n 'AWS::Cognito::UserPool': { nameProperty: 'UserPoolName', options: { maxLength: 128 } },\n 'AWS::ElastiCache::SubnetGroup': {\n nameProperty: 'CacheSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::ElastiCache::CacheCluster': {\n nameProperty: 'ClusterName',\n options: { maxLength: 40, lowercase: true },\n },\n 'AWS::RDS::DBSubnetGroup': {\n nameProperty: 'DBSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::RDS::DBCluster': {\n nameProperty: 'DBClusterIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::RDS::DBInstance': {\n nameProperty: 'DBInstanceIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n // DocumentDB — RDS-shaped API; same name constraints.\n 'AWS::DocDB::DBSubnetGroup': {\n nameProperty: 'DBSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::DocDB::DBCluster': {\n nameProperty: 'DBClusterIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::DocDB::DBInstance': {\n nameProperty: 'DBInstanceIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n // Neptune — RDS-shaped API; same name constraints.\n 'AWS::Neptune::DBSubnetGroup': {\n nameProperty: 'DBSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::Neptune::DBCluster': {\n nameProperty: 'DBClusterIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::Neptune::DBInstance': {\n nameProperty: 'DBInstanceIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::ElasticLoadBalancingV2::LoadBalancer': {\n nameProperty: 'Name',\n options: { maxLength: 32 },\n },\n 'AWS::ElasticLoadBalancingV2::TargetGroup': {\n nameProperty: 'Name',\n options: { maxLength: 32 },\n },\n 'AWS::WAFv2::WebACL': { nameProperty: 'Name', options: { maxLength: 128 } },\n 'AWS::CodeBuild::Project': { nameProperty: 'Name', options: { maxLength: 255 } },\n 'AWS::S3Express::DirectoryBucket': {\n nameProperty: 'BucketName',\n options: { maxLength: 63, lowercase: true },\n },\n};\n\n/**\n * Apply default name generation for CC API fallback.\n *\n * When a resource doesn't have an explicit name property set,\n * generates the same default name that the SDK provider would have created.\n * This ensures consistent naming regardless of whether SDK or CC API handles the resource.\n *\n * @param logicalId Logical ID from the template\n * @param resourceType CloudFormation resource type\n * @param properties Resource properties (will not be mutated)\n * @returns Properties with default name applied if needed, or original properties if no rule exists\n */\nexport function applyDefaultNameForFallback(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n): Record<string, unknown> {\n const rule = FALLBACK_NAME_RULES[resourceType];\n if (!rule) return properties;\n\n // If the name property is already set, no need to generate\n if (properties[rule.nameProperty]) return properties;\n\n const generatedName = generateResourceName(logicalId, rule.options);\n\n return {\n ...properties,\n [rule.nameProperty]: generatedName,\n };\n}\n","/**\n * Live multi-line progress renderer for the bottom of the terminal.\n *\n * Maintains a \"live area\" listing in-flight tasks (Creating MyBucket...),\n * redrawn on a spinner timer. Other log output is routed through\n * {@link LiveRenderer.printAbove} so it appears above the live area without\n * disturbing the currently-displayed in-flight tasks.\n *\n * Design notes:\n * - Multiple resources can be in flight concurrently (cdkd uses parallel DAG\n * dispatch), so a single in-place line overwrite is not enough — each\n * in-flight resource is its own line in the live area.\n * - On non-TTY (CI/log-collection), the renderer stays inactive and\n * {@link LiveRenderer.printAbove} falls through to a direct write, so output\n * matches the previous append-only behavior.\n * - In verbose mode (debug level) the caller should not start the renderer:\n * debug logs would interleave too aggressively with the live area.\n */\n\nimport { getCurrentStackName } from '../provisioning/resource-name.js';\n\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\nconst FRAME_INTERVAL_MS = 80;\nconst ESC = '\\x1b[';\n\ninterface Task {\n label: string;\n startedAt: number;\n /** Stack the task belongs to (from `withStackName` AsyncLocalStorage),\n * or `undefined` when addTask was called outside any stack scope.\n * Captured at addTask time because the spinner re-draw runs on a\n * setInterval timer with no inherited async context. */\n stackName: string | undefined;\n}\n\n/**\n * Scope a task `id` to its calling stack so two stacks running in\n * parallel — `cdkd deploy --all` with `--stack-concurrency > 1` — don't\n * collide on the same `logicalId` in the renderer's task Map. Without\n * this, stack B's `addTask('MyQueue', ...)` would overwrite stack A's\n * entry, and stack A's later `removeTask('MyQueue')` would erase\n * stack B's.\n */\nfunction scopedKey(id: string, stackName: string | undefined): string {\n return stackName ? `${stackName}:${id}` : id;\n}\n\nexport class LiveRenderer {\n private tasks = new Map<string, Task>();\n private active = false;\n private spinnerIndex = 0;\n private interval: NodeJS.Timeout | null = null;\n private linesDrawn = 0;\n private cursorHidden = false;\n private exitListener: (() => void) | null = null;\n private readonly stream: NodeJS.WriteStream;\n\n constructor(stream: NodeJS.WriteStream = process.stdout) {\n this.stream = stream;\n }\n\n isActive(): boolean {\n return this.active;\n }\n\n /**\n * Enable the live renderer. No-op if stdout is not a TTY or if\n * `CDKD_NO_LIVE=1`. Returns true if successfully enabled.\n */\n start(): boolean {\n if (this.active) return true;\n if (!this.stream.isTTY) return false;\n if (process.env['CDKD_NO_LIVE'] === '1') return false;\n\n this.active = true;\n this.hideCursor();\n // Restore the cursor on abrupt process exit (e.g., uncaught exception\n // before stop() runs). Removed in stop() to avoid leaking listeners\n // across renderer instances.\n if (!this.exitListener) {\n this.exitListener = () => this.showCursor();\n process.on('exit', this.exitListener);\n }\n this.interval = setInterval(() => this.draw(), FRAME_INTERVAL_MS);\n if (typeof this.interval.unref === 'function') this.interval.unref();\n return true;\n }\n\n stop(): void {\n if (!this.active) return;\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.clear();\n this.showCursor();\n if (this.exitListener) {\n process.removeListener('exit', this.exitListener);\n this.exitListener = null;\n }\n this.tasks.clear();\n this.active = false;\n }\n\n addTask(id: string, label: string): void {\n const stackName = getCurrentStackName();\n this.tasks.set(scopedKey(id, stackName), { label, startedAt: Date.now(), stackName });\n if (this.active) this.draw();\n }\n\n removeTask(id: string): void {\n const stackName = getCurrentStackName();\n if (!this.tasks.delete(scopedKey(id, stackName))) return;\n if (this.active) this.draw();\n }\n\n /**\n * Replace the label of a previously-added task in place. No-op if the\n * task is not currently tracked (e.g. it already finished). Used by the\n * per-resource deadline wrapper to surface a \"[taking longer than\n * expected, Nm+]\" suffix without disturbing the elapsed-time counter\n * the renderer tracks via `startedAt`.\n */\n updateTaskLabel(id: string, label: string): void {\n const stackName = getCurrentStackName();\n const task = this.tasks.get(scopedKey(id, stackName));\n if (!task) return;\n task.label = label;\n if (this.active) this.draw();\n }\n\n /**\n * Print content above the live area. Clears the live area, runs the writer,\n * then redraws the live area. When the renderer is inactive, the writer\n * runs directly so callers can use this unconditionally.\n */\n printAbove(write: () => void): void {\n if (!this.active) {\n write();\n return;\n }\n this.clear();\n write();\n this.draw();\n }\n\n private clear(): void {\n if (this.linesDrawn === 0) return;\n this.stream.write('\\r');\n for (let i = 0; i < this.linesDrawn; i++) {\n this.stream.write(`${ESC}1A${ESC}2K`);\n }\n this.linesDrawn = 0;\n }\n\n private draw(): void {\n if (!this.active) return;\n this.clear();\n if (this.tasks.size === 0) return;\n\n const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length]!;\n this.spinnerIndex++;\n\n // When tasks from more than one stack are in flight at the same time,\n // prefix each line with the stack name so the user can tell them\n // apart. In single-stack runs, omit the prefix to keep the area\n // visually clean. (Detection is per-frame: as soon as a second\n // stack adds its first task, the prefix appears on every row;\n // when the second stack's tasks all complete and only one stack\n // remains, the prefix disappears.)\n const distinctStacks = new Set<string | undefined>();\n for (const task of this.tasks.values()) distinctStacks.add(task.stackName);\n const showStackPrefix = distinctStacks.size > 1;\n\n // Truncate to terminal width so a long label does not wrap and confuse\n // the line-up clear logic. Default to 80 if columns is unavailable.\n const cols = this.stream.columns ?? 80;\n const lines: string[] = [];\n for (const task of this.tasks.values()) {\n const elapsed = ((Date.now() - task.startedAt) / 1000).toFixed(1);\n const prefix = showStackPrefix && task.stackName ? `[${task.stackName}] ` : '';\n const raw = ` ${frame} ${prefix}${task.label} (${elapsed}s)`;\n lines.push(this.truncate(raw, cols));\n }\n this.stream.write(lines.join('\\n') + '\\n');\n this.linesDrawn = lines.length;\n }\n\n private truncate(s: string, maxLen: number): string {\n if (s.length <= maxLen) return s;\n if (maxLen <= 1) return '…';\n return s.substring(0, maxLen - 1) + '…';\n }\n\n private hideCursor(): void {\n if (this.cursorHidden) return;\n this.stream.write(`${ESC}?25l`);\n this.cursorHidden = true;\n }\n\n private showCursor(): void {\n if (!this.cursorHidden) return;\n this.stream.write(`${ESC}?25h`);\n this.cursorHidden = false;\n }\n}\n\nlet globalRenderer: LiveRenderer | null = null;\n\nexport function getLiveRenderer(): LiveRenderer {\n if (!globalRenderer) globalRenderer = new LiveRenderer();\n return globalRenderer;\n}\n\n/**\n * Reset the singleton (for tests).\n */\nexport function resetLiveRenderer(): void {\n if (globalRenderer) globalRenderer.stop();\n globalRenderer = null;\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * Per-stack log buffer for parallel multi-stack deploys.\n *\n * Without buffering, two concurrent `deploy()` calls interleave their\n * `logger.info(...)` lines: stack A's \"Changes: 4 to create\" appears in\n * the middle of stack B's `[N/N] ✅ ...` progress, and stack B's\n * \"Deployment completed\" lands after stack A's late progress lines. With\n * buffering, each stack's log output is captured into its own buffer\n * for the duration of the deploy and flushed atomically when the deploy\n * finishes — so the user sees one clean per-stack block.\n *\n * Single-stack deploys do NOT enable buffering (the caller checks\n * `stacks.length > 1`); real-time output is preferred when there is no\n * interleaving risk.\n */\nexport interface StackOutputBuffer {\n lines: string[];\n}\n\nconst outputBufferStore = new AsyncLocalStorage<StackOutputBuffer>();\n\n/**\n * Run `fn` with a fresh log buffer scoped to its async chain. Any\n * `logger.info / debug / warn / error` calls inside `fn` (and any\n * `await`s) push into the buffer instead of writing to stdout/stderr.\n * Returns the buffered lines (and either `result` or `error`) so the\n * caller can flush them in one block.\n */\nexport async function runStackBuffered<T>(\n fn: () => Promise<T>\n): Promise<{ lines: string[] } & ({ ok: true; result: T } | { ok: false; error: unknown })> {\n const buffer: StackOutputBuffer = { lines: [] };\n return outputBufferStore.run(buffer, async () => {\n try {\n const result = await fn();\n return { ok: true, result, lines: buffer.lines };\n } catch (error) {\n return { ok: false, error, lines: buffer.lines };\n }\n });\n}\n\n/**\n * Get the current async context's stack output buffer, or `undefined`\n * if no `runStackBuffered` is active. The logger consults this on every\n * call: present → push to buffer; absent → fall through to live\n * renderer / console.\n */\nexport function getCurrentStackOutputBuffer(): StackOutputBuffer | undefined {\n return outputBufferStore.getStore();\n}\n","import type { Logger, LogLevel } from '../types/config.js';\nimport { getLiveRenderer } from './live-renderer.js';\nimport { getCurrentStackOutputBuffer } from './stack-context.js';\n\n/**\n * ANSI color codes\n */\nconst colors = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n} as const;\n\n/**\n * Format timestamp\n */\nfunction formatTimestamp(): string {\n const now = new Date();\n return now.toISOString();\n}\n\n/**\n * Console logger implementation\n *\n * Supports two output modes:\n * - verbose (debug level): timestamps, module prefixes, all details\n * - compact (info level): clean output without timestamps or prefixes\n */\nexport class ConsoleLogger implements Logger {\n private level: LogLevel;\n private useColors: boolean;\n\n constructor(level: LogLevel = 'info', useColors: boolean = true) {\n this.level = level;\n this.useColors = useColors;\n }\n\n private shouldLog(level: LogLevel): boolean {\n const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];\n const currentLevelIndex = levels.indexOf(this.level);\n const messageLevelIndex = levels.indexOf(level);\n return messageLevelIndex >= currentLevelIndex;\n }\n\n private formatMessage(level: LogLevel, message: string, ...args: unknown[]): string {\n const formattedArgs = args.length > 0 ? ' ' + args.map((a) => JSON.stringify(a)).join(' ') : '';\n\n // Verbose mode: full timestamps and level\n if (this.level === 'debug') {\n const timestamp = formatTimestamp();\n const levelStr = level.toUpperCase().padEnd(5);\n\n if (this.useColors) {\n const levelColor = {\n debug: colors.gray,\n info: colors.blue,\n warn: colors.yellow,\n error: colors.red,\n }[level];\n\n return `${colors.dim}${timestamp}${colors.reset} ${levelColor}${levelStr}${colors.reset} ${message}${formattedArgs}`;\n }\n\n return `${timestamp} ${levelStr} ${message}${formattedArgs}`;\n }\n\n // Compact mode: clean output\n if (this.useColors) {\n if (level === 'error') {\n return `${colors.red}${message}${formattedArgs}${colors.reset}`;\n }\n if (level === 'warn') {\n return `${colors.yellow}${message}${formattedArgs}${colors.reset}`;\n }\n return `${message}${formattedArgs}`;\n }\n\n return `${message}${formattedArgs}`;\n }\n\n /**\n * Route a formatted log line. When a per-stack output buffer is active in\n * the current async context (parallel multi-stack deploy), capture the\n * line into the buffer so it can be flushed as one atomic block when the\n * stack finishes. Otherwise fall through to the live renderer / console\n * as before.\n */\n private emit(level: LogLevel, formatted: string): void {\n const buffer = getCurrentStackOutputBuffer();\n if (buffer) {\n buffer.lines.push(formatted);\n return;\n }\n getLiveRenderer().printAbove(() => {\n if (level === 'error') console.error(formatted);\n else if (level === 'warn') console.warn(formatted);\n else if (level === 'info') console.info(formatted);\n else console.debug(formatted);\n });\n }\n\n debug(message: string, ...args: unknown[]): void {\n if (this.shouldLog('debug')) {\n this.emit('debug', this.formatMessage('debug', message, ...args));\n }\n }\n\n info(message: string, ...args: unknown[]): void {\n if (this.shouldLog('info')) {\n this.emit('info', this.formatMessage('info', message, ...args));\n }\n }\n\n warn(message: string, ...args: unknown[]): void {\n if (this.shouldLog('warn')) {\n this.emit('warn', this.formatMessage('warn', message, ...args));\n }\n }\n\n error(message: string, ...args: unknown[]): void {\n if (this.shouldLog('error')) {\n this.emit('error', this.formatMessage('error', message, ...args));\n }\n }\n\n /**\n * Set log level\n */\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n getLevel(): LogLevel {\n return this.level;\n }\n\n /**\n * Create a child logger with a prefix\n *\n * In verbose mode, prefix is shown as [Prefix]. In compact mode, prefix is hidden.\n */\n child(prefix: string): ChildLogger {\n return new ChildLogger(prefix, this.useColors);\n }\n}\n\n/**\n * Child logger that always syncs level from global logger\n */\nclass ChildLogger extends ConsoleLogger {\n private readonly prefix: string;\n\n constructor(prefix: string, useColors: boolean) {\n super('info', useColors);\n this.prefix = prefix;\n }\n\n private syncLevel(): void {\n if (globalLogger) {\n this.setLevel(globalLogger.getLevel());\n }\n }\n\n override debug(message: string, ...args: unknown[]): void {\n this.syncLevel();\n super.debug(`[${this.prefix}] ${message}`, ...args);\n }\n\n override info(message: string, ...args: unknown[]): void {\n this.syncLevel();\n const msg = this.getLevel() === 'debug' ? `[${this.prefix}] ${message}` : message;\n super.info(msg, ...args);\n }\n\n override warn(message: string, ...args: unknown[]): void {\n this.syncLevel();\n const msg = this.getLevel() === 'debug' ? `[${this.prefix}] ${message}` : message;\n super.warn(msg, ...args);\n }\n\n override error(message: string, ...args: unknown[]): void {\n this.syncLevel();\n const msg = this.getLevel() === 'debug' ? `[${this.prefix}] ${message}` : message;\n super.error(msg, ...args);\n }\n}\n\n/**\n * Global logger instance\n */\nlet globalLogger: ConsoleLogger | null = null;\n\n/**\n * Get or create global logger\n */\nexport function getLogger(): ConsoleLogger {\n if (!globalLogger) {\n globalLogger = new ConsoleLogger();\n }\n return globalLogger;\n}\n\n/**\n * Set global logger instance\n */\nexport function setLogger(logger: ConsoleLogger): void {\n globalLogger = logger;\n}\n","import { getLogger } from './logger.js';\n\n/**\n * Base error class for cdkd\n */\nexport class CdkdError 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 = 'CdkdError';\n Object.setPrototypeOf(this, CdkdError.prototype);\n }\n}\n\n/**\n * State management errors\n */\nexport class StateError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'STATE_ERROR', cause);\n this.name = 'StateError';\n Object.setPrototypeOf(this, StateError.prototype);\n }\n}\n\n/**\n * Lock acquisition errors\n */\nexport class LockError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'LOCK_ERROR', cause);\n this.name = 'LockError';\n Object.setPrototypeOf(this, LockError.prototype);\n }\n}\n\n/**\n * Synthesis errors\n */\nexport class SynthesisError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'SYNTHESIS_ERROR', cause);\n this.name = 'SynthesisError';\n Object.setPrototypeOf(this, SynthesisError.prototype);\n }\n}\n\n/**\n * Asset errors\n */\nexport class AssetError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'ASSET_ERROR', cause);\n this.name = 'AssetError';\n Object.setPrototypeOf(this, AssetError.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 * (PR 5) for container Lambdas; the parallel `AssetError` covers the\n * `cdkd publish-assets` / `cdkd deploy` build path. Kept distinct from\n * `AssetError` so `cdkd local invoke` failures don't show up under the\n * \"asset\" error class.\n */\nexport class LocalInvokeBuildError extends CdkdError {\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 * Resource provisioning errors\n */\nexport class ProvisioningError extends CdkdError {\n public readonly resourceType: string;\n public readonly logicalId: string;\n public readonly physicalId: string | undefined;\n\n constructor(\n message: string,\n resourceType: string,\n logicalId: string,\n physicalId?: string,\n cause?: Error\n ) {\n super(message, 'PROVISIONING_ERROR', cause);\n this.resourceType = resourceType;\n this.logicalId = logicalId;\n this.physicalId = physicalId;\n this.name = 'ProvisioningError';\n Object.setPrototypeOf(this, ProvisioningError.prototype);\n }\n}\n\n/**\n * Resource provisioning timeout errors (per-resource wall-clock deadline).\n *\n * Thrown by `withResourceDeadline` when a single CREATE / UPDATE / DELETE\n * operation exceeds the user-configured `--resource-timeout`. The deploy\n * engine catches this, wraps it in {@link ProvisioningError}, and lets the\n * existing failure path (interrupt siblings → pre-rollback save → rollback\n * unless `--no-rollback`) take over.\n *\n * The message intentionally names the resource, type, region, elapsed time\n * and operation, plus how to override the default. Long-running providers\n * (e.g. Custom Resource: 1h polling cap) self-report their needed budget\n * via `getMinResourceTimeoutMs()`, so the user only needs a per-type\n * override (`--resource-timeout TYPE=DURATION`) when they want to bump a\n * specific non-self-reporting type or shorten a self-reported one.\n */\nexport class ResourceTimeoutError extends CdkdError {\n public readonly logicalId: string;\n public readonly resourceType: string;\n public readonly region: string;\n public readonly elapsedMs: number;\n public readonly operation: 'CREATE' | 'UPDATE' | 'DELETE';\n public readonly timeoutMs: number;\n\n constructor(\n logicalId: string,\n resourceType: string,\n region: string,\n elapsedMs: number,\n operation: 'CREATE' | 'UPDATE' | 'DELETE',\n timeoutMs: number\n ) {\n const elapsedLabel = formatDuration(elapsedMs);\n const timeoutLabel = formatDuration(timeoutMs);\n super(\n `Resource ${logicalId} (${resourceType}) in ${region} timed out after ${timeoutLabel} during ${operation} (elapsed ${elapsedLabel}).\\n` +\n 'This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or\\n' +\n `slow ENI provisioning. Re-run with --resource-timeout ${resourceType}=<DURATION>\\n` +\n 'to bump the budget for this resource type only, or --verbose to see the\\n' +\n 'underlying provider activity.',\n 'RESOURCE_TIMEOUT'\n );\n this.logicalId = logicalId;\n this.resourceType = resourceType;\n this.region = region;\n this.elapsedMs = elapsedMs;\n this.operation = operation;\n this.timeoutMs = timeoutMs;\n this.name = 'ResourceTimeoutError';\n Object.setPrototypeOf(this, ResourceTimeoutError.prototype);\n }\n}\n\n/**\n * Format a duration in milliseconds as a short human-readable label\n * (`30m`, `1h30m`, `45s`). Used by {@link ResourceTimeoutError} so the\n * error message stays compact.\n */\nfunction formatDuration(ms: number): string {\n if (ms < 60_000) {\n return `${Math.round(ms / 1000)}s`;\n }\n const totalMinutes = Math.round(ms / 60_000);\n if (totalMinutes < 60) return `${totalMinutes}m`;\n const hours = Math.floor(totalMinutes / 60);\n const minutes = totalMinutes % 60;\n return minutes === 0 ? `${hours}h` : `${hours}h${minutes}m`;\n}\n\n/**\n * Dependency resolution errors\n */\nexport class DependencyError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'DEPENDENCY_ERROR', cause);\n this.name = 'DependencyError';\n Object.setPrototypeOf(this, DependencyError.prototype);\n }\n}\n\n/**\n * Configuration errors\n */\nexport class ConfigError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'CONFIG_ERROR', cause);\n this.name = 'ConfigError';\n Object.setPrototypeOf(this, ConfigError.prototype);\n }\n}\n\n/**\n * Signals a partial-failure outcome that should map to exit code 2 (not 1).\n *\n * Used by `cdkd destroy` and `cdkd state destroy` when one or more\n * per-resource deletes failed but the overall command finished its work\n * (state.json is preserved, the rest of the stack was deleted, and the\n * user can re-run to clean up the remaining resources).\n *\n * Exit code conventions:\n * - 0: command completed successfully, no resources left in error state.\n * - 1: command-level failure (auth error, bad arguments, synth crash,\n * unhandled exception). Default for any thrown error.\n * - 2: partial failure — work completed but some resources are still in\n * an error state. Re-running typically resolves it. Documented in\n * README's \"Exit codes\" section.\n *\n * `handleError` recognizes this class via `instanceof` and uses its\n * `exitCode` instead of the default 1.\n */\nexport class PartialFailureError extends CdkdError {\n readonly exitCode: number = 2;\n\n constructor(message: string, cause?: Error) {\n super(message, 'PARTIAL_FAILURE', cause);\n this.name = 'PartialFailureError';\n Object.setPrototypeOf(this, PartialFailureError.prototype);\n }\n}\n\n/**\n * Signals that a provider cannot perform an in-place `update` for a\n * resource type — most commonly because the AWS resource is structurally\n * immutable (`AWS::Lambda::LayerVersion`, `AWS::S3Tables::TableBucket` once\n * created, certain `AWS::EC2::*` sub-resources) or because the provider\n * surfaces a sub-resource attachment whose only mutation pattern is\n * delete + add (Lambda permission statements, IAM policy attachments).\n *\n * Surfaced through `cdkd drift --revert`, which calls\n * `provider.update(logicalId, physicalId, type, stateProps, awsProps)` to\n * push cdkd state values back into AWS for every drifted resource. When a\n * provider throws this error, the drift command collects it as a\n * per-resource outcome distinct from a generic AWS update failure: the\n * fix is to re-deploy with `--replace` (or recreate the resource), not to\n * retry the update.\n *\n * Carries the same `exitCode = 2` as {@link PartialFailureError} so a\n * drift run that hits one immutable resource is reported as partial-\n * success rather than fatal — the rest of the drifted resources still\n * had their `update` invoked, and the user has a clear next step printed\n * for the unsupported one.\n */\nexport class ResourceUpdateNotSupportedError extends CdkdError {\n readonly exitCode: number = 2;\n public readonly resourceType: string;\n public readonly logicalId: string;\n /**\n * Human-readable hint printed alongside the error. The default is\n * \"use cdkd deploy with --replace, or change the resource definition\n * to create a new version\" — providers are encouraged to override\n * with a more specific suggestion when one is available (e.g.\n * Lambda::Permission's \"delete + add a new statement\").\n */\n public readonly suggestion: string | undefined;\n\n constructor(resourceType: string, logicalId: string, suggestion?: string, cause?: Error) {\n const tail = suggestion\n ? suggestion\n : 'use cdkd deploy with --replace, or change the resource definition to create a new version';\n super(\n `${resourceType} (${logicalId}) cannot be updated in place: ${tail}.`,\n 'RESOURCE_UPDATE_NOT_SUPPORTED',\n cause\n );\n this.resourceType = resourceType;\n this.logicalId = logicalId;\n this.suggestion = suggestion;\n this.name = 'ResourceUpdateNotSupportedError';\n Object.setPrototypeOf(this, ResourceUpdateNotSupportedError.prototype);\n }\n}\n\n/**\n * Signals a refusal to destroy a stack whose CDK manifest has\n * `terminationProtection: true`.\n *\n * Surfaced from `cdkd destroy <stack>` / `cdkd destroy --all` BEFORE\n * any lock acquisition or per-resource delete. In multi-stack runs\n * (e.g. `--all`) this counts as a per-stack failure and the rest of\n * the targets continue — the aggregated count is wrapped in\n * {@link PartialFailureError} so the command exits with code 2.\n *\n * The bypass workflow is documented in the message: edit the CDK code\n * (`new Stack(app, '...', { terminationProtection: false })`),\n * redeploy, then retry the destroy. A future `--remove-protection`\n * flag (separate scope) will provide an explicit one-shot bypass.\n *\n * Note: `cdkd state destroy` (state-only, no synth) does NOT honor\n * `terminationProtection` — the flag is a CDK property not persisted\n * in cdkd's state.json. Use `cdkd destroy` when synth is available.\n */\nexport class StackTerminationProtectionError extends CdkdError {\n public readonly stackName: string;\n\n constructor(stackName: string, cause?: Error) {\n super(\n `Stack '${stackName}' has terminationProtection: true and cannot be destroyed. ` +\n `Set terminationProtection: false in the CDK code, redeploy, then retry 'cdkd destroy ${stackName}'.`,\n 'STACK_TERMINATION_PROTECTION',\n cause\n );\n this.stackName = stackName;\n this.name = 'StackTerminationProtectionError';\n Object.setPrototypeOf(this, StackTerminationProtectionError.prototype);\n }\n}\n\n/**\n * One consumer that still references the producer being destroyed via\n * `Fn::ImportValue`. Surfaced inside {@link StackHasActiveImportsError}.\n */\nexport interface ActiveImportConsumer {\n consumerStack: string;\n consumerRegion: string;\n exportName: string;\n}\n\n/**\n * `cdkd destroy <producer>` refused because at least one consumer stack\n * still records an `Fn::ImportValue` reference to one of the producer's\n * outputs. This matches CloudFormation's strong-reference semantics —\n * CFn rejects `DeleteStack` for an exporter while an importer exists.\n *\n * cdkd has no `--force` escape hatch for this (intentionally, mirroring\n * CFn). The error message lists every offending consumer and points the\n * user at the two valid resolution paths:\n *\n * 1. Destroy the consumer first: `cdkd destroy <consumer>`\n * 2. Remove the `Fn::ImportValue` from the consumer's template and\n * redeploy, then retry the producer destroy.\n *\n * Weak-reference consumers (`Fn::GetStackOutput`, cdkd-specific) never\n * trigger this error by design — the producer stays deletable\n * independently of consumers when the user intentionally chose a weak\n * reference at template-authoring time.\n *\n * Exit code 2 (same as `PartialFailureError`) so multi-stack `cdkd\n * destroy --all` runs that partially succeed still surface as\n * non-zero without being indistinguishable from a fatal cdkd error.\n */\nexport class StackHasActiveImportsError extends CdkdError {\n readonly exitCode: number = 2;\n public readonly producerStack: string;\n public readonly producerRegion: string;\n public readonly consumers: ActiveImportConsumer[];\n\n constructor(\n producerStack: string,\n producerRegion: string,\n consumers: ActiveImportConsumer[],\n cause?: Error\n ) {\n const lines = consumers.map(\n (c) => ` - ${c.consumerStack} (${c.consumerRegion}): imports export '${c.exportName}'`\n );\n super(\n `Cannot destroy stack '${producerStack}' (${producerRegion}): ` +\n `the following stacks still import its outputs via Fn::ImportValue:\\n` +\n `${lines.join('\\n')}\\n\\n` +\n `This matches CloudFormation's strong-reference semantics — exports are\\n` +\n `protected as long as a consumer references them.\\n\\n` +\n `To proceed:\\n` +\n ` 1. Destroy the consumer first: cdkd destroy <consumer-stack>\\n` +\n ` 2. Or remove the Fn::ImportValue from the consumer's template\\n` +\n ` (e.g. inline the value, or refactor) and re-deploy the consumer,\\n` +\n ` then retry this destroy.\\n\\n` +\n `Note: cdkd's Fn::GetStackOutput intrinsic is a weak alternative that\\n` +\n `does NOT protect the producer — use it when you intentionally want\\n` +\n `the producer to be deletable independently of consumers.`,\n 'STACK_HAS_ACTIVE_IMPORTS',\n cause\n );\n this.producerStack = producerStack;\n this.producerRegion = producerRegion;\n this.consumers = consumers;\n this.name = 'StackHasActiveImportsError';\n Object.setPrototypeOf(this, StackHasActiveImportsError.prototype);\n }\n}\n\n/**\n * Signals that `cdkd local 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\n * `AuthType !== 'NONE'` or `InvokeMode === 'RESPONSE_STREAM'`, or an\n * unsupported intrinsic function in `IntegrationUri`.\n *\n * The message names every offending route and points the user at the\n * deferred follow-up PR (8b for authorizers, etc.). Hard-error at\n * discovery so the server never starts in a half-working state.\n */\nexport class RouteDiscoveryError extends CdkdError {\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 an unrecoverable failure inside `cdkd local start-api`'s HTTP\n * server — port-binding failure, RIE returned malformed JSON, container\n * pool acquire timed out, etc. Distinct from {@link RouteDiscoveryError}\n * which fires before the server starts.\n */\nexport class StartApiServerError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'START_API_SERVER_ERROR', cause);\n this.name = 'StartApiServerError';\n Object.setPrototypeOf(this, StartApiServerError.prototype);\n }\n}\n\n/**\n * Signals a `cdkd local run-task` orchestration failure that did not\n * originate from a lower-level module (those throw their own narrower\n * errors — `EcsTaskResolutionError`, `EcsSecretsResolutionError`,\n * `DockerRunnerError`, `LocalInvokeBuildError`). Used by the runner /\n * CLI when the failure is meaningful only at the task-orchestrator\n * layer (e.g. cyclic dependsOn, essential container did not start).\n */\nexport class LocalRunTaskError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'LOCAL_RUN_TASK_ERROR', cause);\n this.name = 'LocalRunTaskError';\n Object.setPrototypeOf(this, LocalRunTaskError.prototype);\n }\n}\n\n/**\n * Check if error is a cdkd error\n */\nexport function isCdkdError(error: unknown): error is CdkdError {\n return error instanceof CdkdError;\n}\n\n/**\n * Format error for display\n */\nexport function formatError(error: unknown): string {\n if (isCdkdError(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). `PartialFailureError`\n * overrides it to 2 so callers can distinguish \"command crashed /\n * unauthorized / bad arguments\" from \"command completed but some\n * resources are still in an error state, re-run to clean up\".\n *\n * A {@link CdkdError} subclass may set `silent = true` to suppress the\n * default `logger.error` line — used by `cdkd drift` where the command\n * has already printed a richer report and only needs the exit code.\n */\nexport function handleError(error: unknown): never {\n const logger = getLogger();\n const silent = error instanceof CdkdError && (error as CdkdError & { 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 const exitCode = error instanceof PartialFailureError ? error.exitCode : 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 `cdkd 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 { GetBucketLocationCommand, S3Client } from '@aws-sdk/client-s3';\n\n/**\n * Per-bucket region cache.\n *\n * Storing the in-flight `Promise` (rather than the resolved value) collapses\n * concurrent calls for the same bucket into a single `GetBucketLocation`\n * request — the second caller awaits the same promise instead of issuing a\n * duplicate API call.\n */\nconst cache = new Map<string, Promise<string>>();\n\n/**\n * Options accepted by {@link resolveBucketRegion}.\n *\n * `profile` and `credentials` mirror the AWS SDK shape so callers can pass\n * the same auth configuration the rest of cdkd uses.\n *\n * `fallbackRegion` is returned if `GetBucketLocation` fails for any reason —\n * the resolver never throws so a missing/forbidden bucket does not block the\n * caller from surfacing a more useful downstream error (e.g. the actionable\n * `normalizeAwsError` message).\n */\nexport interface ResolveBucketRegionOptions {\n profile?: string;\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n };\n fallbackRegion?: string;\n}\n\n/**\n * Resolve the AWS region of an S3 bucket via `GetBucketLocation`.\n *\n * Why `GetBucketLocation` rather than `HeadBucket`:\n * AWS SDK v3's region-redirect middleware does not handle the empty-body\n * HEAD response on a 301 cross-region redirect cleanly — the protocol\n * parser falls through to `getErrorSchemaOrThrowBaseException` and\n * produces a synthetic `name: 'Unknown', message: 'UnknownError'`.\n * `GetBucketLocation` is a GET with an XML body and is not subject to the\n * same SDK glitch.\n *\n * Why a region-agnostic client (us-east-1):\n * `GetBucketLocation` works against the global S3 endpoint regardless of\n * the bucket's actual region, so we don't need to know the answer to ask\n * the question.\n *\n * The result is cached per bucket name for the process lifetime — bucket\n * regions never move, so the cache never needs invalidation.\n *\n * @returns The bucket's region (e.g. `us-west-2`). An empty `LocationConstraint`\n * in the response means `us-east-1` (S3 quirk). On any error, returns\n * `opts.fallbackRegion` if provided, else `us-east-1`.\n */\nexport async function resolveBucketRegion(\n bucketName: string,\n opts: ResolveBucketRegionOptions = {}\n): Promise<string> {\n const cached = cache.get(bucketName);\n if (cached) return cached;\n\n const promise = (async (): Promise<string> => {\n const client = new S3Client({\n region: 'us-east-1',\n ...(opts.profile && { profile: opts.profile }),\n ...(opts.credentials && { credentials: opts.credentials }),\n });\n try {\n const response = await client.send(new GetBucketLocationCommand({ Bucket: bucketName }));\n // Empty / null `LocationConstraint` is S3's way of saying us-east-1.\n return response.LocationConstraint || 'us-east-1';\n } catch {\n // The resolver never throws: cdkd would rather surface the actionable\n // downstream error (HeadBucket → `normalizeAwsError`) than mask it\n // behind a noisy GetBucketLocation failure.\n return opts.fallbackRegion ?? 'us-east-1';\n } finally {\n client.destroy();\n }\n })();\n\n cache.set(bucketName, promise);\n return promise;\n}\n\n/**\n * Clear the per-bucket region cache. Used by tests to reset state between\n * cases — production code never needs to call this.\n */\nexport function clearBucketRegionCache(): void {\n cache.clear();\n}\n","import { spawn } from 'node:child_process';\nimport { writeFileSync, mkdtempSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { getLogger } from '../utils/logger.js';\nimport { SynthesisError } from '../utils/error-handler.js';\n\n/**\n * Options for CDK app execution\n */\nexport interface AppExecutorOptions {\n /** CDK app command (e.g., \"node app.ts\") */\n app: string;\n\n /** Output directory for cloud assembly (default: \"cdk.out\") */\n outputDir: string;\n\n /** Context key-value pairs to pass to the app */\n context: Record<string, unknown>;\n\n /** AWS region */\n region?: string;\n\n /** AWS account ID */\n accountId?: string;\n}\n\n/** Cloud assembly schema version compatible with CDK v2 */\nconst CDK_ASM_VERSION = '38.0.0';\n\n/** Maximum context size before overflow to temp file (32KB) */\nconst CONTEXT_OVERFLOW_LIMIT = 32 * 1024;\n\n/**\n * Executes CDK app as subprocess to produce a cloud assembly\n */\nexport class AppExecutor {\n private logger = getLogger().child('AppExecutor');\n\n /**\n * Execute CDK app and produce cloud assembly in outputDir\n */\n async execute(options: AppExecutorOptions): Promise<void> {\n const { app, outputDir, context, region, accountId } = options;\n\n this.logger.debug('Executing CDK app:', app);\n this.logger.debug('Output directory:', outputDir);\n\n // Build environment variables\n const env: Record<string, string> = {\n ...process.env,\n CDK_OUTDIR: outputDir,\n };\n\n if (region) {\n env['CDK_DEFAULT_REGION'] = region;\n }\n if (accountId) {\n env['CDK_DEFAULT_ACCOUNT'] = accountId;\n }\n\n // Cloud assembly version and CLI version for compatibility\n env['CDK_CLI_ASM_VERSION'] = CDK_ASM_VERSION;\n env['CDK_CLI_VERSION'] = '2.1000.0';\n\n // Pass context via environment variable or temp file\n let contextTempDir: string | undefined;\n const contextJson = JSON.stringify(context);\n\n if (Buffer.byteLength(contextJson, 'utf-8') > CONTEXT_OVERFLOW_LIMIT) {\n // Context too large: write to temp file\n contextTempDir = mkdtempSync(join(tmpdir(), 'cdkd-context-'));\n const contextFile = join(contextTempDir, 'context.json');\n writeFileSync(contextFile, contextJson, 'utf-8');\n env['CONTEXT_OVERFLOW_LOCATION_ENV'] = contextFile;\n this.logger.debug('Context overflow: written to temp file');\n } else {\n env['CDK_CONTEXT_JSON'] = contextJson;\n }\n\n // Determine executable\n const commandLine = this.guessExecutable(app);\n this.logger.debug('Command line:', commandLine);\n\n try {\n await this.spawn(commandLine, env);\n this.logger.debug('CDK app execution completed');\n } finally {\n // Clean up temp context file\n if (contextTempDir) {\n try {\n rmSync(contextTempDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n }\n\n /**\n * Determine how to execute the app command\n * - If it's a .js file, prepend node\n * - Otherwise execute as shell command\n */\n private guessExecutable(app: string): string {\n const trimmed = app.trim();\n\n // If it ends with .js, prepend the current node executable\n if (trimmed.endsWith('.js') || trimmed.split(/\\s+/)[0]?.endsWith('.js')) {\n const parts = trimmed.split(/\\s+/);\n parts[0] = `\"${process.execPath}\" \"${parts[0]}\"`;\n return parts.join(' ');\n }\n\n return trimmed;\n }\n\n /**\n * Spawn subprocess and wait for completion\n */\n private spawn(commandLine: string, env: Record<string, string>): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(commandLine, {\n stdio: ['ignore', 'pipe', 'pipe'],\n shell: true,\n env,\n cwd: process.cwd(),\n });\n\n const stderrChunks: string[] = [];\n\n proc.stdout?.on('data', (data: Buffer) => {\n const line = data.toString().trim();\n if (line) {\n this.logger.debug('[app stdout]', line);\n }\n });\n\n proc.stderr?.on('data', (data: Buffer) => {\n const line = data.toString().trim();\n if (line) {\n stderrChunks.push(line);\n // CDK bundling progress and warnings come through stderr\n this.logger.info(line);\n }\n });\n\n proc.on('error', (error) => {\n reject(new SynthesisError(`Failed to execute CDK app: ${error.message}`, error));\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n const stderr = stderrChunks.join('\\n');\n reject(\n new SynthesisError(\n `CDK app exited with code ${code}${stderr ? `\\n\\nstderr:\\n${stderr}` : ''}`\n )\n );\n }\n });\n });\n }\n}\n","/**\n * Cloud Assembly types\n *\n * Based on CDK Cloud Assembly manifest format.\n * These types replace @aws-cdk/cloud-assembly-api dependency.\n */\n\n/**\n * Cloud Assembly manifest (manifest.json)\n */\nexport interface AssemblyManifest {\n /** Cloud assembly schema version */\n version: string;\n\n /** Artifacts in the assembly */\n artifacts?: Record<string, ArtifactManifest>;\n\n /** Missing context values that need to be resolved */\n missing?: MissingContext[];\n\n /** Runtime information */\n runtime?: RuntimeInfo;\n}\n\n/**\n * Artifact manifest entry\n */\nexport interface ArtifactManifest {\n /** Artifact type */\n type: ArtifactType;\n\n /** Target environment (e.g., \"aws://123456789012/us-east-1\") */\n environment?: string;\n\n /**\n * Hierarchical display name (e.g., \"MyStage/MyStack\" for stacks under a Stage,\n * or just \"MyStack\" at the top level). Set by CDK synth.\n */\n displayName?: string;\n\n /** Artifact-specific properties */\n properties?: Record<string, unknown>;\n\n /** Dependencies on other artifacts (by artifact ID) */\n dependencies?: string[];\n\n /** Metadata entries */\n metadata?: Record<string, MetadataEntry[]>;\n}\n\n/**\n * Artifact types\n */\nexport type ArtifactType =\n | 'aws:cloudformation:stack'\n | 'cdk:asset-manifest'\n | 'cdk:tree'\n | 'cdk:cloud-assembly'\n | 'cdk:feature-flag-report';\n\n/**\n * CloudFormation stack artifact properties\n */\nexport interface StackArtifactProperties {\n /** Path to template file relative to assembly directory */\n templateFile: string;\n\n /** Physical stack name */\n stackName?: string;\n\n /** Stack parameters */\n parameters?: Record<string, string>;\n\n /** Stack tags */\n tags?: Record<string, string>;\n\n /** Role to assume for deployment */\n assumeRoleArn?: string;\n\n /** CloudFormation execution role */\n cloudFormationExecutionRoleArn?: string;\n\n /** Termination protection */\n terminationProtection?: boolean;\n}\n\n/**\n * Asset manifest artifact properties\n */\nexport interface AssetManifestArtifactProperties {\n /** Path to asset manifest file relative to assembly directory */\n file: string;\n\n /** Required bootstrap stack version */\n requiresBootstrapStackVersion?: number;\n}\n\n/**\n * Missing context entry\n */\nexport interface MissingContext {\n /** Context key */\n key: string;\n\n /** Context provider type */\n provider: string;\n\n /** Provider-specific query properties */\n props: ContextQueryProperties;\n}\n\n/**\n * Base context query properties (all providers extend this)\n */\nexport interface ContextQueryProperties {\n /** Target AWS account */\n account: string;\n\n /** Target AWS region */\n region: string;\n\n /** Role to assume for lookup */\n lookupRoleArn?: string;\n\n /** Additional properties (provider-specific) */\n [key: string]: unknown;\n}\n\n/**\n * Metadata entry in artifact\n */\nexport interface MetadataEntry {\n type: string;\n data?: unknown;\n trace?: string[];\n}\n\n/**\n * Runtime information\n */\nexport interface RuntimeInfo {\n libraries?: Record<string, string>;\n}\n\n/**\n * Parsed environment from artifact\n */\nexport interface ArtifactEnvironment {\n account: string;\n region: string;\n}\n\n/**\n * Parse environment string \"aws://account/region\"\n */\nexport function parseEnvironment(env: string): ArtifactEnvironment {\n const match = env.match(/^aws:\\/\\/([^/]+)\\/(.+)$/);\n if (!match) {\n return { account: 'unknown-account', region: 'unknown-region' };\n }\n return {\n account: match[1] === 'unknown-account' ? 'unknown-account' : match[1]!,\n region: match[2] === 'unknown-region' ? 'unknown-region' : match[2]!,\n };\n}\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type {\n AssemblyManifest,\n ArtifactManifest,\n StackArtifactProperties,\n AssetManifestArtifactProperties,\n ArtifactEnvironment,\n} from '../types/assembly.js';\nimport { parseEnvironment } from '../types/assembly.js';\nimport type { CloudFormationTemplate } from '../types/resource.js';\nimport { getLogger } from '../utils/logger.js';\nimport { SynthesisError } from '../utils/error-handler.js';\n\n/**\n * Stack information extracted from cloud assembly\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\" for stacks\n * under a Stage, or \"MyStack\" at the top level). Falls back to `stackName` when\n * the assembly does not carry one.\n */\n displayName: string;\n\n /** Artifact ID in manifest */\n artifactId: string;\n\n /** CloudFormation template */\n template: CloudFormationTemplate;\n\n /** Asset manifest file path (absolute) */\n assetManifestPath?: string | undefined;\n\n /** Stack dependency names (other stacks this stack depends on) */\n dependencyNames: string[];\n\n /** Target region from CDK environment */\n region?: string | undefined;\n\n /** Target account from CDK environment */\n account?: string | undefined;\n\n /**\n * Stack-level termination protection (CDK `Stack.terminationProtection`).\n *\n * When `true`, `cdkd destroy <stack>` and `cdkd destroy --all` refuse to\n * destroy this stack and surface a `StackTerminationProtectionError` so\n * the CLI exits via the partial-failure path (exit 2) without invoking\n * the per-resource delete loop. The bypass workflow is to set\n * `terminationProtection: false` in the CDK code, redeploy, then retry.\n *\n * Read-only `cdkd diff` and forward-only `cdkd deploy` are unaffected.\n * `cdkd state destroy` (state-only, no synth) cannot honor this — the\n * flag is a CDK property not stored in cdkd state.json.\n */\n terminationProtection?: boolean | undefined;\n}\n\n/**\n * Reads and parses Cloud Assembly from cdk.out directory\n */\nexport class AssemblyReader {\n private logger = getLogger().child('AssemblyReader');\n\n /**\n * Read manifest.json from assembly directory\n */\n readManifest(assemblyDir: string): AssemblyManifest {\n const manifestPath = join(assemblyDir, 'manifest.json');\n\n try {\n const content = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as AssemblyManifest;\n this.logger.debug(`Loaded manifest: version=${manifest.version}`);\n return manifest;\n } catch (error) {\n throw new SynthesisError(\n `Failed to read cloud assembly manifest from ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get all stacks from assembly (recursively traverses nested assemblies / Stages)\n */\n getAllStacks(assemblyDir: string, manifest: AssemblyManifest): StackInfo[] {\n if (!manifest.artifacts) {\n this.logger.warn('No artifacts found in manifest');\n return [];\n }\n\n // Build map of artifact ID → asset manifest path\n const assetManifestMap = this.buildAssetManifestMap(assemblyDir, manifest);\n\n const stacks: StackInfo[] = [];\n\n for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) {\n if (artifact.type === 'aws:cloudformation:stack') {\n const stackInfo = this.extractStackInfo(\n assemblyDir,\n artifactId,\n artifact,\n manifest,\n assetManifestMap\n );\n stacks.push(stackInfo);\n } else if (artifact.type === 'cdk:cloud-assembly') {\n // Nested assembly (Stage) — recurse into subdirectory\n const props = artifact.properties as { directoryName?: string } | undefined;\n if (props?.directoryName) {\n const nestedDir = join(assemblyDir, props.directoryName);\n try {\n const nestedManifest = this.readManifest(nestedDir);\n const nestedStacks = this.getAllStacks(nestedDir, nestedManifest);\n stacks.push(...nestedStacks);\n } catch (error) {\n this.logger.warn(\n `Failed to read nested assembly '${props.directoryName}': ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n }\n }\n\n this.logger.debug(`Found ${stacks.length} stack(s) in assembly`);\n return stacks;\n }\n\n /**\n * Get a specific stack by name\n */\n getStack(assemblyDir: string, manifest: AssemblyManifest, stackName: string): StackInfo {\n const stacks = this.getAllStacks(assemblyDir, manifest);\n const stack = stacks.find((s) => s.stackName === stackName);\n\n if (!stack) {\n throw new SynthesisError(\n `Stack '${stackName}' not found in assembly. Available: ${stacks.map((s) => s.stackName).join(', ')}`\n );\n }\n\n return stack;\n }\n\n /**\n * Get template for a specific stack\n */\n getTemplate(\n assemblyDir: string,\n manifest: AssemblyManifest,\n stackName: string\n ): CloudFormationTemplate {\n return this.getStack(assemblyDir, manifest, stackName).template;\n }\n\n /**\n * Build map: stack artifact ID → asset manifest absolute path\n */\n private buildAssetManifestMap(\n assemblyDir: string,\n manifest: AssemblyManifest\n ): Map<string, string> {\n const map = new Map<string, string>();\n\n if (!manifest.artifacts) return map;\n\n for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) {\n if (artifact.type !== 'cdk:asset-manifest') continue;\n\n const props = artifact.properties as AssetManifestArtifactProperties | undefined;\n if (props?.file) {\n map.set(artifactId, join(assemblyDir, props.file));\n }\n }\n\n return map;\n }\n\n /**\n * Extract stack info from artifact\n */\n private extractStackInfo(\n assemblyDir: string,\n artifactId: string,\n artifact: ArtifactManifest,\n manifest: AssemblyManifest,\n assetManifestMap: Map<string, string>\n ): StackInfo {\n const props = artifact.properties as StackArtifactProperties | undefined;\n const stackName = props?.stackName || artifactId;\n\n // Load template\n const templateFile = props?.templateFile;\n if (!templateFile) {\n throw new SynthesisError(`Stack '${stackName}' has no templateFile property`);\n }\n\n const templatePath = join(assemblyDir, templateFile);\n let template: CloudFormationTemplate;\n try {\n const content = readFileSync(templatePath, 'utf-8');\n template = JSON.parse(content) as CloudFormationTemplate;\n } catch (error) {\n throw new SynthesisError(\n `Failed to read template for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n this.logger.debug(\n `Stack: ${stackName}, Resources: ${Object.keys(template.Resources ?? {}).length}`\n );\n\n // Find asset manifest for this stack\n let assetManifestPath: string | undefined;\n if (artifact.dependencies) {\n for (const depId of artifact.dependencies) {\n if (assetManifestMap.has(depId)) {\n assetManifestPath = assetManifestMap.get(depId);\n this.logger.debug(`Found asset manifest for ${stackName}: ${depId}`);\n break;\n }\n }\n }\n\n // Extract stack dependencies (other stacks, not asset manifests)\n const dependencyNames: string[] = [];\n if (artifact.dependencies && manifest.artifacts) {\n for (const depId of artifact.dependencies) {\n const depArtifact = manifest.artifacts[depId];\n if (depArtifact?.type === 'aws:cloudformation:stack') {\n const depProps = depArtifact.properties as StackArtifactProperties | undefined;\n const depName = depProps?.stackName || depId;\n if (depName !== stackName) {\n dependencyNames.push(depName);\n }\n }\n }\n }\n\n if (dependencyNames.length > 0) {\n this.logger.debug(`Stack '${stackName}' depends on: [${dependencyNames.join(', ')}]`);\n }\n\n // Parse environment\n let env: ArtifactEnvironment | undefined;\n if (artifact.environment) {\n env = parseEnvironment(artifact.environment);\n }\n\n return {\n stackName,\n displayName: artifact.displayName ?? stackName,\n artifactId,\n template,\n assetManifestPath,\n dependencyNames,\n region: env?.region !== 'unknown-region' ? env?.region : undefined,\n account: env?.account !== 'unknown-account' ? env?.account : undefined,\n ...(props?.terminationProtection !== undefined && {\n terminationProtection: props.terminationProtection,\n }),\n };\n }\n\n /**\n * Check if stack has assets\n */\n hasAssets(stackInfo: StackInfo): boolean {\n return stackInfo.assetManifestPath !== undefined;\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { getLogger } from '../utils/logger.js';\n\nconst CDK_CONTEXT_FILE = 'cdk.context.json';\n\n/**\n * Manages reading and writing of cdk.context.json\n *\n * Context values resolved by context providers are persisted here\n * so they don't need to be re-fetched on subsequent synthesis runs.\n * Format is compatible with CDK CLI.\n */\nexport class ContextStore {\n private logger = getLogger().child('ContextStore');\n\n /**\n * Load context values from cdk.context.json\n *\n * @param cwd Working directory (default: process.cwd())\n * @returns Context key-value map, or empty object if file doesn't exist\n */\n load(cwd?: string): Record<string, unknown> {\n const filePath = resolve(cwd || process.cwd(), CDK_CONTEXT_FILE);\n\n if (!existsSync(filePath)) {\n this.logger.debug('No cdk.context.json found');\n return {};\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const context = JSON.parse(content) as Record<string, unknown>;\n this.logger.debug(\n `Loaded ${Object.keys(context).length} context value(s) from cdk.context.json`\n );\n return context;\n } catch (error) {\n this.logger.warn(\n `Failed to parse cdk.context.json: ${error instanceof Error ? error.message : String(error)}`\n );\n return {};\n }\n }\n\n /**\n * Save resolved context values to cdk.context.json\n *\n * Merges with existing values. Transient values (errors) are excluded.\n *\n * @param updates Key-value pairs to save\n * @param cwd Working directory (default: process.cwd())\n */\n save(updates: Record<string, unknown>, cwd?: string): void {\n const filePath = resolve(cwd || process.cwd(), CDK_CONTEXT_FILE);\n\n // Load existing values\n const existing = this.load(cwd);\n\n // Merge, excluding transient values (provider errors)\n for (const [key, value] of Object.entries(updates)) {\n if (this.isTransient(value)) {\n this.logger.debug(`Skipping transient context value for key: ${key}`);\n continue;\n }\n existing[key] = value;\n }\n\n // Write back\n writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\\n', 'utf-8');\n this.logger.debug(`Saved ${Object.keys(updates).length} context value(s) to cdk.context.json`);\n }\n\n /**\n * Check if a context value is transient (should not be persisted)\n *\n * CDK CLI marks provider errors with $dontSaveContext: true\n */\n private isTransient(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false;\n return (value as Record<string, unknown>)['$dontSaveContext'] === true;\n }\n}\n","import { EC2Client, DescribeAvailabilityZonesCommand } from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Availability Zones context provider\n *\n * Returns available AZ names for a region.\n * CDK provider type: \"availability-zones\"\n */\nexport class AZContextProvider implements ContextProvider {\n private logger = getLogger().child('AZContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<string[]> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n\n this.logger.debug(`Fetching availability zones for region: ${region}`);\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(new DescribeAvailabilityZonesCommand({}));\n\n const azs = (response.AvailabilityZones ?? [])\n .filter((az) => az.State === 'available')\n .map((az) => az.ZoneName!)\n .filter(Boolean)\n .sort();\n\n this.logger.debug(`Found ${azs.length} availability zones: ${azs.join(', ')}`);\n return azs;\n } finally {\n client.destroy();\n }\n }\n}\n","import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * SSM Parameter context provider\n *\n * Reads SSM parameter values.\n * CDK provider type: \"ssm\"\n */\nexport class SSMContextProvider implements ContextProvider {\n private logger = getLogger().child('SSMContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const parameterName = props['parameterName'] as string;\n\n if (!parameterName) {\n throw new Error('SSM context provider requires parameterName property');\n }\n\n this.logger.debug(`Reading SSM parameter: ${parameterName} (region: ${region})`);\n\n const client = new SSMClient({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(new GetParameterCommand({ Name: parameterName }));\n\n if (!response.Parameter || response.Parameter.Value === undefined) {\n // Check if we should suppress this error\n const suppressError = props['ignoreErrorOnMissingContext'] === true;\n if (suppressError && 'dummyValue' in props) {\n this.logger.debug(`SSM parameter not found, returning dummy value`);\n return props['dummyValue'];\n }\n throw new Error(`SSM parameter not found: ${parameterName}`);\n }\n\n this.logger.debug(`SSM parameter resolved: ${parameterName}`);\n return response.Parameter.Value;\n } finally {\n client.destroy();\n }\n }\n}\n","import {\n Route53Client,\n ListHostedZonesByNameCommand,\n GetHostedZoneCommand,\n} from '@aws-sdk/client-route-53';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Hosted Zone context provider\n *\n * Looks up Route53 hosted zones by domain name.\n * CDK provider type: \"hosted-zone\"\n */\nexport class HostedZoneContextProvider implements ContextProvider {\n private logger = getLogger().child('HostedZoneContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const domainName = props['domainName'] as string;\n const privateZone = props['privateZone'] as boolean | undefined;\n const vpcId = props['vpcId'] as string | undefined;\n\n if (!domainName) {\n throw new Error('Hosted zone context provider requires domainName property');\n }\n\n this.logger.debug(`Looking up hosted zone: ${domainName} (private: ${privateZone})`);\n\n const client = new Route53Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(\n new ListHostedZonesByNameCommand({\n DNSName: domainName,\n MaxItems: 10,\n })\n );\n\n const zones = response.HostedZones ?? [];\n\n // Filter by domain name (exact match with trailing dot)\n const normalizedDomain = domainName.endsWith('.') ? domainName : `${domainName}.`;\n const matching = zones.filter((z) => z.Name === normalizedDomain);\n\n // Filter by private/public\n let filtered = matching;\n if (privateZone !== undefined) {\n filtered = matching.filter((z) => z.Config?.PrivateZone === privateZone);\n }\n\n // Filter by VPC (for private zones)\n if (vpcId && filtered.length > 0) {\n const vpcFiltered = [];\n for (const zone of filtered) {\n const zoneDetail = await client.send(new GetHostedZoneCommand({ Id: zone.Id }));\n const zoneVpcs = zoneDetail.VPCs ?? [];\n if (zoneVpcs.some((v) => v.VPCId === vpcId)) {\n vpcFiltered.push(zone);\n }\n }\n filtered = vpcFiltered;\n }\n\n if (filtered.length === 0) {\n throw new Error(\n `No hosted zone found for domain: ${domainName}` +\n (privateZone !== undefined ? ` (private: ${privateZone})` : '') +\n (vpcId ? ` (vpcId: ${vpcId})` : '')\n );\n }\n\n if (filtered.length > 1) {\n throw new Error(\n `Multiple hosted zones found for domain: ${domainName}. ` +\n `Found: ${filtered.map((z) => z.Id).join(', ')}`\n );\n }\n\n const zone = filtered[0]!;\n // Strip /hostedzone/ prefix from ID\n const zoneId = zone.Id!.replace('/hostedzone/', '');\n\n this.logger.debug(`Resolved hosted zone: ${zoneId} (${zone.Name})`);\n\n return {\n Id: zoneId,\n Name: zone.Name,\n };\n } finally {\n client.destroy();\n }\n }\n}\n","import {\n EC2Client,\n DescribeVpcsCommand,\n DescribeSubnetsCommand,\n DescribeRouteTablesCommand,\n DescribeVpnGatewaysCommand,\n type Filter,\n type Subnet,\n} from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * VPC context provider\n *\n * Discovers VPC details including subnets, route tables, and AZs.\n * CDK provider type: \"vpc-provider\"\n */\nexport class VpcContextProvider implements ContextProvider {\n private logger = getLogger().child('VpcContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const filter = props['filter'] as Record<string, string> | undefined;\n const returnAsymmetricSubnets = props['returnAsymmetricSubnets'] as boolean | undefined;\n const subnetGroupNameTag = (props['subnetGroupNameTag'] as string) || 'aws-cdk:subnet-name';\n const returnVpnGateways = props['returnVpnGateways'] as boolean | undefined;\n\n this.logger.debug(`Looking up VPC (region: ${region}, filter: ${JSON.stringify(filter)})`);\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n // 1. Find VPC\n const vpcFilters: Filter[] = filter\n ? Object.entries(filter).map(([name, value]) => ({\n Name: name,\n Values: [String(value)],\n }))\n : [];\n\n const vpcsResponse = await client.send(new DescribeVpcsCommand({ Filters: vpcFilters }));\n\n const vpcs = vpcsResponse.Vpcs ?? [];\n if (vpcs.length === 0) {\n throw new Error(`No VPC found matching filter: ${JSON.stringify(filter)}`);\n }\n if (vpcs.length > 1) {\n throw new Error(\n `Multiple VPCs found matching filter: ${JSON.stringify(filter)}. ` +\n `Found: ${vpcs.map((v) => v.VpcId).join(', ')}`\n );\n }\n\n const vpc = vpcs[0]!;\n const vpcId = vpc.VpcId!;\n this.logger.debug(`Found VPC: ${vpcId}`);\n\n // 2. Get subnets\n const subnetsResponse = await client.send(\n new DescribeSubnetsCommand({\n Filters: [{ Name: 'vpc-id', Values: [vpcId] }],\n })\n );\n const subnets = subnetsResponse.Subnets ?? [];\n\n // 3. Get route tables\n const rtResponse = await client.send(\n new DescribeRouteTablesCommand({\n Filters: [{ Name: 'vpc-id', Values: [vpcId] }],\n })\n );\n const routeTables = rtResponse.RouteTables ?? [];\n\n // Build subnet → route table mapping\n const subnetRouteTableMap = new Map<string, string>();\n let mainRouteTableId: string | undefined;\n for (const rt of routeTables) {\n for (const assoc of rt.Associations ?? []) {\n if (assoc.Main) {\n mainRouteTableId = rt.RouteTableId;\n }\n if (assoc.SubnetId && rt.RouteTableId) {\n subnetRouteTableMap.set(assoc.SubnetId, rt.RouteTableId);\n }\n }\n }\n\n // 4. Classify subnets\n const routeTableInfos = routeTables.map((rt) => ({\n routeTableId: rt.RouteTableId ?? '',\n routes: (rt.Routes ?? []).map((r) => ({\n gatewayId: r.GatewayId,\n natGatewayId: r.NatGatewayId,\n })),\n }));\n\n const classifiedSubnets = this.classifySubnets(\n subnets,\n subnetRouteTableMap,\n mainRouteTableId,\n routeTableInfos,\n subnetGroupNameTag\n );\n\n // Sort by AZ for consistent ordering\n const sortByAz = (a: SubnetInfo, b: SubnetInfo) => a.az.localeCompare(b.az);\n\n const publicSubnets = classifiedSubnets.filter((s) => s.type === 'Public').sort(sortByAz);\n const privateSubnets = classifiedSubnets.filter((s) => s.type === 'Private').sort(sortByAz);\n const isolatedSubnets = classifiedSubnets.filter((s) => s.type === 'Isolated').sort(sortByAz);\n\n // 5. Get VPN gateway (optional)\n let vpnGatewayId: string | undefined;\n if (returnVpnGateways !== false) {\n const vpnResponse = await client.send(\n new DescribeVpnGatewaysCommand({\n Filters: [\n { Name: 'attachment.vpc-id', Values: [vpcId] },\n { Name: 'attachment.state', Values: ['attached'] },\n ],\n })\n );\n vpnGatewayId = vpnResponse.VpnGateways?.[0]?.VpnGatewayId;\n }\n\n // 6. Build result\n const azs = [...new Set(subnets.map((s) => s.AvailabilityZone!))].sort();\n\n const result: Record<string, unknown> = {\n vpcId,\n vpcCidrBlock: vpc.CidrBlock,\n ownerAccountId: vpc.OwnerId,\n availabilityZones: azs,\n publicSubnetIds: publicSubnets.map((s) => s.subnetId),\n publicSubnetNames: publicSubnets.map((s) => s.name),\n publicSubnetRouteTableIds: publicSubnets.map((s) => s.routeTableId),\n privateSubnetIds: privateSubnets.map((s) => s.subnetId),\n privateSubnetNames: privateSubnets.map((s) => s.name),\n privateSubnetRouteTableIds: privateSubnets.map((s) => s.routeTableId),\n isolatedSubnetIds: isolatedSubnets.map((s) => s.subnetId),\n isolatedSubnetNames: isolatedSubnets.map((s) => s.name),\n isolatedSubnetRouteTableIds: isolatedSubnets.map((s) => s.routeTableId),\n };\n\n if (vpnGatewayId) {\n result['vpnGatewayId'] = vpnGatewayId;\n }\n\n if (returnAsymmetricSubnets) {\n result['subnetGroups'] = this.buildSubnetGroups(classifiedSubnets);\n }\n\n this.logger.debug(\n `VPC ${vpcId}: ${publicSubnets.length} public, ${privateSubnets.length} private, ${isolatedSubnets.length} isolated subnets`\n );\n\n return result;\n } finally {\n client.destroy();\n }\n }\n\n /**\n * Classify subnets as Public, Private, or Isolated\n */\n private classifySubnets(\n subnets: Subnet[],\n subnetRouteTableMap: Map<string, string>,\n mainRouteTableId: string | undefined,\n routeTables: {\n routeTableId: string;\n routes: { gatewayId?: string | undefined; natGatewayId?: string | undefined }[];\n }[],\n subnetGroupNameTag: string\n ): SubnetInfo[] {\n // Build route table → has IGW/NAT mapping\n const rtHasIgw = new Map<string, boolean>();\n const rtHasNat = new Map<string, boolean>();\n for (const rt of routeTables) {\n const hasIgw = rt.routes.some((r) => r.gatewayId?.startsWith('igw-'));\n const hasNat = rt.routes.some((r) => r.natGatewayId?.startsWith('nat-'));\n rtHasIgw.set(rt.routeTableId, hasIgw);\n rtHasNat.set(rt.routeTableId, hasNat);\n }\n\n return subnets.map((subnet) => {\n const subnetId = subnet.SubnetId!;\n const az = subnet.AvailabilityZone!;\n const routeTableId = subnetRouteTableMap.get(subnetId) || mainRouteTableId || '';\n\n // Determine type from tags first\n const tags = subnet.Tags ?? [];\n const nameTag = tags.find((t) => t.Key === subnetGroupNameTag);\n let name = nameTag?.Value || '';\n\n // Determine type\n let type: 'Public' | 'Private' | 'Isolated';\n if (nameTag?.Value) {\n // Trust tag-based classification\n const lowerName = nameTag.Value.toLowerCase();\n if (lowerName.includes('public')) {\n type = 'Public';\n } else if (lowerName.includes('private')) {\n type = 'Private';\n } else if (lowerName.includes('isolated')) {\n type = 'Isolated';\n } else {\n // Fall back to route analysis\n type = this.classifyByRoute(routeTableId, rtHasIgw, rtHasNat, subnet);\n }\n } else {\n type = this.classifyByRoute(routeTableId, rtHasIgw, rtHasNat, subnet);\n name = type;\n }\n\n return { subnetId, az, routeTableId, type, name };\n });\n }\n\n private classifyByRoute(\n routeTableId: string,\n rtHasIgw: Map<string, boolean>,\n rtHasNat: Map<string, boolean>,\n subnet: Subnet\n ): 'Public' | 'Private' | 'Isolated' {\n if (rtHasIgw.get(routeTableId) || subnet.MapPublicIpOnLaunch) {\n return 'Public';\n }\n if (rtHasNat.get(routeTableId)) {\n return 'Private';\n }\n return 'Isolated';\n }\n\n /**\n * Build subnet groups for asymmetric subnet support\n */\n private buildSubnetGroups(subnets: SubnetInfo[]): unknown[] {\n const groups = new Map<string, SubnetInfo[]>();\n for (const subnet of subnets) {\n const key = `${subnet.type}/${subnet.name}`;\n const group = groups.get(key) ?? [];\n group.push(subnet);\n groups.set(key, group);\n }\n\n return Array.from(groups.entries()).map(([, groupSubnets]) => ({\n name: groupSubnets[0]!.name,\n type: groupSubnets[0]!.type,\n subnets: groupSubnets\n .sort((a, b) => a.az.localeCompare(b.az))\n .map((s) => ({\n subnetId: s.subnetId,\n availabilityZone: s.az,\n routeTableId: s.routeTableId,\n })),\n }));\n }\n}\n\ninterface SubnetInfo {\n subnetId: string;\n az: string;\n routeTableId: string;\n type: 'Public' | 'Private' | 'Isolated';\n name: string;\n}\n","import {\n CloudControlClient,\n GetResourceCommand,\n ListResourcesCommand,\n} from '@aws-sdk/client-cloudcontrol';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Cloud Control API context provider\n *\n * Generic provider that uses Cloud Control API to lookup any resource type.\n * CDK provider type: \"cc-api-provider\"\n *\n * Used by CDK for lookups like IAM Roles, ECR repositories, RDS instances, etc.\n */\nexport class CcApiContextProvider implements ContextProvider {\n private logger = getLogger().child('CcApiContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const typeName = props['typeName'] as string;\n const exactIdentifier = props['exactIdentifier'] as string | undefined;\n const propertiesToReturn = (props['propertiesToReturn'] as string[]) || [];\n const propertyMatch = props['propertyMatch'] as Record<string, unknown> | undefined;\n const expectedMatchCount = (props['expectedMatchCount'] as string) || 'exactly-one';\n const dummyValue = props['dummyValue'];\n const ignoreErrorOnMissingContext = props['ignoreErrorOnMissingContext'] as boolean | undefined;\n\n if (!typeName) {\n throw new Error('CC API context provider requires typeName property');\n }\n\n this.logger.debug(\n `CC API lookup: ${typeName}${exactIdentifier ? ` (id: ${exactIdentifier})` : ''} (region: ${region})`\n );\n\n const client = new CloudControlClient({\n ...(region && { region }),\n });\n\n try {\n let resources: ResourceModel[];\n\n if (exactIdentifier) {\n // Get specific resource by identifier\n const resource = await this.getResource(client, typeName, exactIdentifier);\n resources = resource ? [resource] : [];\n } else {\n // List resources and filter\n resources = await this.listResources(client, typeName);\n\n // Apply property match filter\n if (propertyMatch && Object.keys(propertyMatch).length > 0) {\n resources = resources.filter((r) => this.matchesProperties(r, propertyMatch));\n }\n }\n\n // Validate match count\n this.validateMatchCount(resources, expectedMatchCount, typeName, exactIdentifier);\n\n if (resources.length === 0) {\n if (ignoreErrorOnMissingContext && dummyValue !== undefined) {\n this.logger.debug(`No resources found, returning dummy value`);\n return dummyValue;\n }\n throw new Error(\n `No ${typeName} resource found${exactIdentifier ? ` with identifier ${exactIdentifier}` : ''}`\n );\n }\n\n // Extract requested properties\n if (resources.length === 1) {\n return this.extractProperties(resources[0]!, propertiesToReturn);\n }\n\n return resources.map((r) => this.extractProperties(r, propertiesToReturn));\n } finally {\n client.destroy();\n }\n }\n\n /**\n * Get a single resource by identifier\n */\n private async getResource(\n client: CloudControlClient,\n typeName: string,\n identifier: string\n ): Promise<ResourceModel | null> {\n try {\n const response = await client.send(\n new GetResourceCommand({\n TypeName: typeName,\n Identifier: identifier,\n })\n );\n\n if (!response.ResourceDescription?.Properties) {\n return null;\n }\n\n return JSON.parse(response.ResourceDescription.Properties) as ResourceModel;\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * List all resources of a type\n */\n private async listResources(\n client: CloudControlClient,\n typeName: string\n ): Promise<ResourceModel[]> {\n const resources: ResourceModel[] = [];\n let nextToken: string | undefined;\n\n do {\n const response = await client.send(\n new ListResourcesCommand({\n TypeName: typeName,\n ...(nextToken && { NextToken: nextToken }),\n })\n );\n\n for (const desc of response.ResourceDescriptions ?? []) {\n if (desc.Properties) {\n resources.push(JSON.parse(desc.Properties) as ResourceModel);\n }\n }\n\n nextToken = response.NextToken;\n } while (nextToken);\n\n return resources;\n }\n\n /**\n * Check if resource matches property filter\n */\n private matchesProperties(\n resource: ResourceModel,\n propertyMatch: Record<string, unknown>\n ): boolean {\n for (const [key, expectedValue] of Object.entries(propertyMatch)) {\n const actualValue = this.getNestedProperty(resource, key);\n if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Get nested property value using dot notation\n */\n private getNestedProperty(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split('.');\n let current: unknown = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n }\n\n /**\n * Validate that the number of matches meets expectations\n */\n private validateMatchCount(\n resources: ResourceModel[],\n expectedMatchCount: string,\n typeName: string,\n identifier?: string\n ): void {\n const count = resources.length;\n const context = identifier ? ` with identifier ${identifier}` : '';\n\n switch (expectedMatchCount) {\n case 'exactly-one':\n if (count !== 1) {\n throw new Error(`Expected exactly one ${typeName}${context}, found ${count}`);\n }\n break;\n case 'at-least-one':\n if (count < 1) {\n throw new Error(`Expected at least one ${typeName}${context}, found none`);\n }\n break;\n case 'at-most-one':\n if (count > 1) {\n throw new Error(`Expected at most one ${typeName}${context}, found ${count}`);\n }\n break;\n case 'any':\n // No validation needed\n break;\n }\n }\n\n /**\n * Extract requested properties from resource model\n */\n private extractProperties(\n resource: ResourceModel,\n propertiesToReturn: string[]\n ): Record<string, unknown> {\n if (propertiesToReturn.length === 0) {\n return resource;\n }\n\n const result: Record<string, unknown> = {};\n for (const prop of propertiesToReturn) {\n result[prop] = this.getNestedProperty(resource, prop);\n }\n return result;\n }\n}\n\ntype ResourceModel = Record<string, unknown>;\n","import { EC2Client, DescribeImagesCommand } from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * AMI context provider\n *\n * Searches for the most recent AMI matching filters.\n * CDK provider type: \"ami\"\n */\nexport class AmiContextProvider implements ContextProvider {\n private logger = getLogger().child('AmiContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<string> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const owners = props['owners'] as string[] | undefined;\n const filters = props['filters'] as Record<string, string[]> | undefined;\n\n this.logger.debug(`Looking up AMI (region: ${region})`);\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n const ec2Filters = filters\n ? Object.entries(filters).map(([name, values]) => ({ Name: name, Values: values }))\n : undefined;\n\n const response = await client.send(\n new DescribeImagesCommand({\n ...(owners && { Owners: owners }),\n ...(ec2Filters && { Filters: ec2Filters }),\n })\n );\n\n const images = (response.Images ?? [])\n .filter((img) => img.ImageId && img.CreationDate)\n .sort((a, b) => (b.CreationDate ?? '').localeCompare(a.CreationDate ?? ''));\n\n if (images.length === 0) {\n throw new Error('No AMI found matching the specified filters');\n }\n\n const imageId = images[0]!.ImageId!;\n this.logger.debug(`Resolved AMI: ${imageId}`);\n return imageId;\n } finally {\n client.destroy();\n }\n }\n}\n","import { EC2Client, DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Security Group context provider\n *\n * Looks up security group details by ID.\n * CDK provider type: \"security-group\"\n */\nexport class SecurityGroupContextProvider implements ContextProvider {\n private logger = getLogger().child('SecurityGroupContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const securityGroupId = props['securityGroupId'] as string | undefined;\n const securityGroupName = props['securityGroupName'] as string | undefined;\n const vpcId = props['vpcId'] as string | undefined;\n\n this.logger.debug(\n `Looking up security group (id: ${securityGroupId}, name: ${securityGroupName}, region: ${region})`\n );\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n const filters = [];\n if (securityGroupId) {\n filters.push({ Name: 'group-id', Values: [securityGroupId] });\n }\n if (securityGroupName) {\n filters.push({ Name: 'group-name', Values: [securityGroupName] });\n }\n if (vpcId) {\n filters.push({ Name: 'vpc-id', Values: [vpcId] });\n }\n\n const response = await client.send(\n new DescribeSecurityGroupsCommand({\n ...(filters.length > 0 && { Filters: filters }),\n ...(securityGroupId && !securityGroupName && { GroupIds: [securityGroupId] }),\n })\n );\n\n const groups = response.SecurityGroups ?? [];\n if (groups.length === 0) {\n throw new Error(\n `No security group found (id: ${securityGroupId}, name: ${securityGroupName})`\n );\n }\n\n const sg = groups[0]!;\n this.logger.debug(`Resolved security group: ${sg.GroupId}`);\n\n return {\n securityGroupId: sg.GroupId,\n allowAllOutbound: (sg.IpPermissionsEgress ?? []).some(\n (perm) =>\n perm.IpProtocol === '-1' && (perm.IpRanges ?? []).some((r) => r.CidrIp === '0.0.0.0/0')\n ),\n };\n } finally {\n client.destroy();\n }\n }\n}\n","import {\n ElasticLoadBalancingV2Client,\n DescribeLoadBalancersCommand,\n DescribeListenersCommand,\n} from '@aws-sdk/client-elastic-load-balancing-v2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Load Balancer context provider\n *\n * Looks up ALB/NLB details.\n * CDK provider type: \"load-balancer\"\n */\nexport class LoadBalancerContextProvider implements ContextProvider {\n private logger = getLogger().child('LoadBalancerContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const loadBalancerArn = props['loadBalancerArn'] as string | undefined;\n const loadBalancerType = props['loadBalancerType'] as string | undefined;\n\n this.logger.debug(`Looking up load balancer (arn: ${loadBalancerArn}, region: ${region})`);\n\n const client = new ElasticLoadBalancingV2Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(\n new DescribeLoadBalancersCommand({\n ...(loadBalancerArn && { LoadBalancerArns: [loadBalancerArn] }),\n })\n );\n\n let lbs = response.LoadBalancers ?? [];\n\n if (loadBalancerType) {\n lbs = lbs.filter((lb) => lb.Type === loadBalancerType);\n }\n\n if (lbs.length === 0) {\n throw new Error(`No load balancer found (arn: ${loadBalancerArn})`);\n }\n\n const lb = lbs[0]!;\n this.logger.debug(`Resolved load balancer: ${lb.LoadBalancerArn}`);\n\n return {\n loadBalancerArn: lb.LoadBalancerArn,\n loadBalancerCanonicalHostedZoneId: lb.CanonicalHostedZoneId,\n loadBalancerDnsName: lb.DNSName,\n vpcId: lb.VpcId,\n securityGroupIds: lb.SecurityGroups ?? [],\n ipAddressType: lb.IpAddressType,\n };\n } finally {\n client.destroy();\n }\n }\n}\n\n/**\n * Load Balancer Listener context provider\n *\n * Looks up ALB/NLB listener details.\n * CDK provider type: \"load-balancer-listener\"\n */\nexport class LoadBalancerListenerContextProvider implements ContextProvider {\n private logger = getLogger().child('LoadBalancerListenerContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const listenerArn = props['listenerArn'] as string | undefined;\n const loadBalancerArn = props['loadBalancerArn'] as string | undefined;\n const listenerPort = props['listenerPort'] as number | undefined;\n const listenerProtocol = props['listenerProtocol'] as string | undefined;\n\n this.logger.debug(\n `Looking up load balancer listener (arn: ${listenerArn}, lb: ${loadBalancerArn}, region: ${region})`\n );\n\n const client = new ElasticLoadBalancingV2Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(\n new DescribeListenersCommand({\n ...(listenerArn && { ListenerArns: [listenerArn] }),\n ...(loadBalancerArn && { LoadBalancerArn: loadBalancerArn }),\n })\n );\n\n let listeners = response.Listeners ?? [];\n\n if (listenerPort) {\n listeners = listeners.filter((l) => l.Port === listenerPort);\n }\n if (listenerProtocol) {\n listeners = listeners.filter((l) => l.Protocol === listenerProtocol);\n }\n\n if (listeners.length === 0) {\n throw new Error(\n `No listener found (arn: ${listenerArn}, lb: ${loadBalancerArn}, port: ${listenerPort})`\n );\n }\n\n const listener = listeners[0]!;\n this.logger.debug(`Resolved listener: ${listener.ListenerArn}`);\n\n return {\n listenerArn: listener.ListenerArn,\n listenerPort: listener.Port,\n securityGroupIds: [] as string[],\n };\n } finally {\n client.destroy();\n }\n }\n}\n","import { KMSClient, ListAliasesCommand } from '@aws-sdk/client-kms';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * KMS Key context provider\n *\n * Looks up KMS key by alias name.\n * CDK provider type: \"key-provider\"\n */\nexport class KeyContextProvider implements ContextProvider {\n private logger = getLogger().child('KeyContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const aliasName = props['aliasName'] as string;\n\n if (!aliasName) {\n throw new Error('Key context provider requires aliasName property');\n }\n\n this.logger.debug(`Looking up KMS key by alias: ${aliasName} (region: ${region})`);\n\n const client = new KMSClient({\n ...(region && { region }),\n });\n\n try {\n // Normalize alias name\n const normalizedAlias = aliasName.startsWith('alias/') ? aliasName : `alias/${aliasName}`;\n\n let nextMarker: string | undefined;\n do {\n const response = await client.send(\n new ListAliasesCommand({\n ...(nextMarker && { Marker: nextMarker }),\n })\n );\n\n const match = (response.Aliases ?? []).find((a) => a.AliasName === normalizedAlias);\n if (match) {\n if (!match.TargetKeyId) {\n throw new Error(`KMS alias '${aliasName}' found but has no target key`);\n }\n this.logger.debug(`Resolved KMS key: ${match.TargetKeyId} (alias: ${aliasName})`);\n return { keyId: match.TargetKeyId };\n }\n\n nextMarker = response.NextMarker;\n } while (nextMarker);\n\n throw new Error(`No KMS key found with alias: ${aliasName}`);\n } finally {\n client.destroy();\n }\n }\n}\n","import type { MissingContext } from '../../types/assembly.js';\nimport { getLogger } from '../../utils/logger.js';\nimport { AZContextProvider } from './az-provider.js';\nimport { SSMContextProvider } from './ssm-provider.js';\nimport { HostedZoneContextProvider } from './hosted-zone-provider.js';\nimport { VpcContextProvider } from './vpc-provider.js';\nimport { CcApiContextProvider } from './cc-api-provider.js';\nimport { AmiContextProvider } from './ami-provider.js';\nimport { SecurityGroupContextProvider } from './security-group-provider.js';\nimport {\n LoadBalancerContextProvider,\n LoadBalancerListenerContextProvider,\n} from './load-balancer-provider.js';\nimport { KeyContextProvider } from './key-provider.js';\n\nconst PROVIDER_ERROR_KEY = '$providerError';\nconst TRANSIENT_CONTEXT_KEY = '$dontSaveContext';\n\n/**\n * Context provider interface\n */\nexport interface ContextProvider {\n /**\n * Resolve context value from AWS SDK\n * @param props Provider-specific query properties\n * @returns Resolved context value\n */\n resolve(props: Record<string, unknown>): Promise<unknown>;\n}\n\n/**\n * AWS client configuration for context providers\n */\nexport interface ContextProviderAwsConfig {\n region?: string;\n profile?: string;\n}\n\n/**\n * Context provider registry\n *\n * Maps provider type names to implementations.\n * Resolves missing context values by calling AWS SDK APIs.\n */\nexport class ContextProviderRegistry {\n private logger = getLogger().child('ContextProviderRegistry');\n private providers = new Map<string, ContextProvider>();\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n // Register built-in providers\n this.register('availability-zones', new AZContextProvider(awsConfig));\n this.register('ssm', new SSMContextProvider(awsConfig));\n this.register('hosted-zone', new HostedZoneContextProvider(awsConfig));\n this.register('vpc-provider', new VpcContextProvider(awsConfig));\n this.register('cc-api-provider', new CcApiContextProvider(awsConfig));\n this.register('ami', new AmiContextProvider(awsConfig));\n this.register('security-group', new SecurityGroupContextProvider(awsConfig));\n this.register('load-balancer', new LoadBalancerContextProvider(awsConfig));\n this.register('load-balancer-listener', new LoadBalancerListenerContextProvider(awsConfig));\n this.register('key-provider', new KeyContextProvider(awsConfig));\n }\n\n /**\n * Register a context provider\n */\n register(name: string, provider: ContextProvider): void {\n this.providers.set(name, provider);\n }\n\n /**\n * Resolve all missing context values\n *\n * @param missing Array of missing context entries from manifest\n * @returns Map of context key → resolved value\n */\n async resolve(missing: MissingContext[]): Promise<Record<string, unknown>> {\n const results: Record<string, unknown> = {};\n\n for (const entry of missing) {\n const provider = this.providers.get(entry.provider);\n\n if (!provider) {\n this.logger.warn(`No context provider registered for: ${entry.provider}`);\n results[entry.key] = {\n [PROVIDER_ERROR_KEY]: `Unknown context provider: ${entry.provider}`,\n [TRANSIENT_CONTEXT_KEY]: true,\n };\n continue;\n }\n\n try {\n this.logger.debug(`Resolving context: ${entry.provider} (key: ${entry.key})`);\n const value = await provider.resolve(entry.props);\n results[entry.key] = value;\n this.logger.debug(`Resolved context: ${entry.key}`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`Context provider '${entry.provider}' failed: ${message}`);\n results[entry.key] = {\n [PROVIDER_ERROR_KEY]: message,\n [TRANSIENT_CONTEXT_KEY]: true,\n };\n }\n }\n\n return results;\n }\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { resolve, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * CDK configuration loaded from cdk.json and environment variables\n */\nexport interface CdkConfig {\n app?: string;\n output?: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * cdkd-specific configuration extracted from cdk.json context or environment\n */\nexport interface CdkdConfig {\n stateBucket?: string;\n}\n\n/**\n * Load a JSON config file and return as CdkConfig, or null if not found.\n */\nfunction loadJsonConfig(filePath: string): CdkConfig | null {\n const logger = getLogger();\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const config = JSON.parse(content) as CdkConfig;\n logger.debug(`Loaded config from ${filePath}`);\n return config;\n } catch (error) {\n logger.warn(\n `Failed to parse ${filePath}: ${error instanceof Error ? error.message : String(error)}`\n );\n return null;\n }\n}\n\n/**\n * Load cdk.json from the current working directory\n */\nexport function loadCdkJson(cwd?: string): CdkConfig | null {\n const dir = cwd || process.cwd();\n return loadJsonConfig(resolve(dir, 'cdk.json'));\n}\n\n/**\n * Load user-level defaults from ~/.cdk.json\n *\n * CDK CLI reads this as user-level defaults (lowest priority).\n * Context values from ~/.cdk.json are merged below project cdk.json context.\n */\nexport function loadUserCdkJson(): CdkConfig | null {\n return loadJsonConfig(join(homedir(), '.cdk.json'));\n}\n\n/**\n * Resolve the --app option from CLI, cdk.json, or environment\n *\n * Priority: CLI option > CDKD_APP env > cdk.json app field\n */\nexport function resolveApp(cliApp?: string): string | undefined {\n if (cliApp) return cliApp;\n\n const envApp = process.env['CDKD_APP'];\n if (envApp) return envApp;\n\n const cdkJson = loadCdkJson();\n return cdkJson?.app ?? undefined;\n}\n\n/**\n * Source of a resolved state-bucket name.\n *\n * Reported by `cdkd state info` so users can see *why* a particular bucket was\n * chosen. The CLI flag wins over the env var, which wins over cdk.json, which\n * falls through to a default name derived from the STS account id.\n */\nexport type StateBucketSource = 'cli-flag' | 'env' | 'cdk.json' | 'default' | 'default-legacy';\n\n/**\n * Result of resolving the state bucket, including the source that won.\n */\nexport interface ResolvedStateBucket {\n bucket: string;\n source: StateBucketSource;\n}\n\n/**\n * Resolve the `--capture-observed-state` / `--no-capture-observed-state`\n * option's effective value, falling through to `cdk.json\n * context.cdkd.captureObservedState` when the CLI flag was not passed.\n *\n * Commander reports `--no-X` flags by emitting `x: false` (which the deploy\n * command's TS type carries as `captureObservedState: boolean`). We can't\n * tell from that whether the user explicitly opted out vs. accepted the\n * default `true`, so the cdk.json fallback only fires when the CLI value\n * is the implicit default (`true`). Pass `--no-capture-observed-state`\n * to overrule a `cdk.json: { captureObservedState: true }` explicitly.\n */\nexport function resolveCaptureObservedState(cliValue: boolean): boolean {\n if (cliValue === false) return false;\n const cdkJson = loadCdkJson();\n const cdkdContext = cdkJson?.context?.['cdkd'] as Record<string, unknown> | undefined;\n const v = cdkdContext?.['captureObservedState'];\n if (typeof v === 'boolean') return v;\n return true;\n}\n\n/**\n * Resolve the effective value for \"should cdkd skip the stack-name\n * prefix on user-supplied physical names?\" on `cdkd deploy`.\n *\n * Returns `true` when cdkd should SKIP prepending the stack name to\n * user-declared physical names (e.g. an `iam.Role` whose `roleName:\n * 'my-role'` was set explicitly by the user). Returns `false` when\n * cdkd should KEEP the legacy behavior of prepending the stack name\n * (the pre-v0.94.0 default; now an explicit opt-in).\n *\n * **Default flipped in v0.94.0** ([#299](https://github.com/go-to-k/cdkd/issues/299)).\n * Prior to v0.94.0 the default was `false` (= legacy prefixing) and\n * `--no-prefix-user-supplied-names` was the opt-in. Now the default\n * is `true` (= unprefixed) and `--prefix-user-supplied-names` is the\n * opt-in to restore legacy prefixing. Deploying a CDK app with\n * `roleName: 'my-role'` produces an AWS resource named `my-role` by\n * default; consistent across every resource type out of the box.\n *\n * Auto-generated names (where the user did NOT supply a physical\n * name) are unaffected — every provider's `generateResourceName`\n * call sets `userSupplied: false` on the logical-id fallback path,\n * so the prefix stays for those resources regardless of this flag.\n *\n * Resolution chain (highest wins):\n *\n * 1. `--prefix-user-supplied-names` CLI flag → Commander emits\n * `prefixUserSuppliedNames: true` when the flag is passed.\n * That explicit opt-in to legacy prefixing short-circuits the\n * lookup and returns `false` regardless of env / cdk.json.\n * 2. `CDKD_PREFIX_USER_SUPPLIED_NAMES=true` env var → also returns\n * `false` (= keep legacy prefixing).\n * 3. `cdk.json` `context.cdkd.prefixUserSuppliedNames: true` →\n * same effect.\n * 4. Deprecated `--no-prefix-user-supplied-names` CLI flag (Commander\n * emits `noPrefixUserSuppliedNames: false`) → no-op vs the new\n * default; emits a deprecation warning. Pre-v0.94.0 this was\n * the way to opt in to skipping the prefix; now it matches the\n * default and is kept only for backward-compat / scripts that\n * already set it.\n * 5. Deprecated `CDKD_NO_PREFIX_USER_SUPPLIED_NAMES=true` env var\n * and `cdk.json context.cdkd.noPrefixUserSuppliedNames: true` →\n * same deprecation-warning + no-op semantics.\n * 6. Default `true` (skip prefix — new default in v0.94.0).\n *\n * Mirrors {@link resolveCaptureObservedState}'s pattern. The cliValue\n * argument carries the Commander-emitted boolean for\n * `--prefix-user-supplied-names`. The deprecated\n * `--no-prefix-user-supplied-names` flag is detected via the pre-parse\n * argv walk in {@link warnDeprecatedNoPrefixCliFlag} — NOT here, because\n * declaring both flag forms as separate Commander Options collapses\n * them onto a single key (`noPrefixUserSuppliedNames` would be\n * permanently `undefined` at runtime). Commander's automatic `--no-X`\n * negation still parses the deprecated form without error; it just\n * negates `prefixUserSuppliedNames` to its default `false` (= skip\n * prefix), which matches the new v0.94.0 default semantically.\n */\nexport interface ResolveSkipPrefixOptions {\n /**\n * Commander-emitted value of `--prefix-user-supplied-names` (the new\n * opt-in to legacy prefixing). `true` when the user passed the flag;\n * `false` (= default) when they did not. When `true`, cdkd KEEPS\n * legacy prefixing and {@link resolveSkipPrefix} returns `false`.\n */\n prefixUserSuppliedNames?: boolean;\n}\n\n/**\n * Pre-parse argv walk that surfaces the deprecation warning when the\n * user explicitly passes the legacy `--no-prefix-user-supplied-names`\n * flag. Commander's auto-negation of `--prefix-user-supplied-names`\n * accepts the flag without surfacing it as a distinct option key, so\n * this walk is the only way to catch it for the warning. Call once at\n * the top of every deploy invocation, before {@link resolveSkipPrefix}.\n *\n * Matches the literal `--no-prefix-user-supplied-names` token (and its\n * `--no-prefix-user-supplied-names=<value>` form) so scripts that pass\n * the flag with an explicit value still see the warning.\n */\nexport function warnDeprecatedNoPrefixCliFlag(argv: readonly string[] = process.argv): void {\n const seen = argv.some(\n (a) =>\n a === '--no-prefix-user-supplied-names' || a.startsWith('--no-prefix-user-supplied-names=')\n );\n if (seen) {\n getLogger().warn(\n '--no-prefix-user-supplied-names is deprecated since v0.94.0 — ' +\n 'skipping the prefix is now the default. Remove the flag.'\n );\n }\n}\n\nexport function resolveSkipPrefix(opts: ResolveSkipPrefixOptions = {}): boolean {\n const logger = getLogger();\n\n // Tier 1: --prefix-user-supplied-names CLI flag → keep legacy\n // prefixing. Wins over every other source.\n if (opts.prefixUserSuppliedNames === true) {\n return false;\n }\n\n // Tier 2: CDKD_PREFIX_USER_SUPPLIED_NAMES=true env var → also keep\n // legacy prefixing.\n const envPrefix = process.env['CDKD_PREFIX_USER_SUPPLIED_NAMES'];\n if (envPrefix === 'true') {\n return false;\n }\n\n // Tier 3: cdk.json context.cdkd.prefixUserSuppliedNames: true →\n // same effect.\n const cdkJson = loadCdkJson();\n const cdkdContext = cdkJson?.context?.['cdkd'] as Record<string, unknown> | undefined;\n const v = cdkdContext?.['prefixUserSuppliedNames'];\n if (typeof v === 'boolean' && v === true) {\n return false;\n }\n\n // Deprecated CDKD_NO_PREFIX_USER_SUPPLIED_NAMES env var +\n // cdk.json context.cdkd.noPrefixUserSuppliedNames: emit a\n // deprecation warning when set; they now match the default and are\n // no-ops in effect. (The CLI-flag equivalent is detected via\n // warnDeprecatedNoPrefixCliFlag — see the docstring above.)\n const deprecatedEnv = process.env['CDKD_NO_PREFIX_USER_SUPPLIED_NAMES'];\n if (deprecatedEnv === 'true') {\n logger.warn(\n 'CDKD_NO_PREFIX_USER_SUPPLIED_NAMES is deprecated since v0.94.0 — ' +\n 'skipping the prefix is now the default. Unset the env var.'\n );\n }\n const deprecatedCdkJson = cdkdContext?.['noPrefixUserSuppliedNames'];\n if (typeof deprecatedCdkJson === 'boolean' && deprecatedCdkJson === true) {\n logger.warn(\n 'cdk.json context.cdkd.noPrefixUserSuppliedNames is deprecated since v0.94.0 — ' +\n 'skipping the prefix is now the default. Remove the entry.'\n );\n }\n\n // Tier 6: default → skip prefix (the v0.94.0 flip).\n return true;\n}\n\n/**\n * Resolve the --state-bucket option from CLI, cdk.json context, or environment\n *\n * Priority: CLI option > CDKD_STATE_BUCKET env > cdk.json context.cdkd.stateBucket\n */\nexport function resolveStateBucket(cliBucket?: string): string | undefined {\n return resolveStateBucketWithSource(cliBucket)?.bucket;\n}\n\n/**\n * Like {@link resolveStateBucket}, but also reports which source provided the\n * value. Returns `undefined` when no synchronous source is configured (caller\n * should fall back to the STS-derived default).\n */\nexport function resolveStateBucketWithSource(cliBucket?: string): ResolvedStateBucket | undefined {\n if (cliBucket) return { bucket: cliBucket, source: 'cli-flag' };\n\n const envBucket = process.env['CDKD_STATE_BUCKET'];\n if (envBucket) return { bucket: envBucket, source: 'env' };\n\n const cdkJson = loadCdkJson();\n const cdkdContext = cdkJson?.context?.['cdkd'] as Record<string, unknown> | undefined;\n const bucket = cdkdContext?.['stateBucket'];\n if (typeof bucket === 'string') return { bucket, source: 'cdk.json' };\n\n return undefined;\n}\n\n/**\n * Generate default state bucket name from account info.\n *\n * Format: `cdkd-state-{accountId}` (region intentionally omitted).\n *\n * S3 bucket names are globally unique, so embedding the profile region in the\n * default name made teammates with different profile regions look up\n * different buckets and silently fork their state. Dropping the region from\n * the default lets the whole team converge on a single bucket — its actual\n * region is auto-detected at runtime via `GetBucketLocation`\n * ({@link import('../utils/aws-region-resolver.js').resolveBucketRegion}).\n */\nexport function getDefaultStateBucketName(accountId: string): string {\n return `cdkd-state-${accountId}`;\n}\n\n/**\n * Generate the **legacy** default state bucket name.\n *\n * Format: `cdkd-state-{accountId}-{region}` — the pre-v0.8 default.\n *\n * Used only by the backwards-compatibility fallback in\n * {@link resolveStateBucketWithDefault}: if the new region-free bucket is not\n * found, cdkd checks the legacy region-suffixed name so users who already\n * bootstrapped under the old default keep working until they migrate.\n *\n * TODO(remove-bc-after-1.x): Remove this helper and all callers when the\n * backwards-compat read path is dropped (tracked in PR 99 of the\n * region/state refactor — see `docs/plans/04-state-bucket-naming.md`).\n */\nexport function getLegacyStateBucketName(accountId: string, region: string): string {\n return `cdkd-state-${accountId}-${region}`;\n}\n\n/**\n * Resolve state bucket with STS fallback.\n *\n * Priority:\n * 1. Explicit value from `--state-bucket` / `CDKD_STATE_BUCKET` /\n * `cdk.json context.cdkd.stateBucket` — used as-is.\n * 2. Default name `cdkd-state-{accountId}` (new). Verified to exist via\n * `HeadBucket` against a region-agnostic S3 client (the actual region is\n * resolved separately by {@link\n * import('../utils/aws-region-resolver.js').resolveBucketRegion}).\n * 3. Legacy name `cdkd-state-{accountId}-{region}` — only consulted if step 2\n * returned `NoSuchBucket` / 404. Logs a deprecation warning.\n * 4. Neither found → throw a \"run cdkd bootstrap\" error pointing at the new\n * name.\n *\n * `region` is the CLI's *profile* region; it is used only to construct the\n * legacy fallback name. The actual state-bucket region is resolved later by\n * `resolveBucketRegion`, so the caller does not need to pass the bucket's\n * real region here.\n *\n * Requires AWS credentials to be configured (STS GetCallerIdentity).\n *\n * The bucket name is logged at debug level only — it includes the AWS account\n * id, which would leak via screenshots / public CI logs if printed by default.\n * Use `cdkd state info` to inspect on demand, or pass `--verbose` to surface\n * it in routine commands.\n */\nexport async function resolveStateBucketWithDefault(\n cliBucket: string | undefined,\n region: string\n): Promise<string> {\n return (await resolveStateBucketWithDefaultAndSource(cliBucket, region)).bucket;\n}\n\n/**\n * Like {@link resolveStateBucketWithDefault}, but also reports which source\n * provided the value (`'cli-flag'` / `'env'` / `'cdk.json'` / `'default'` /\n * `'default-legacy'`).\n */\nexport async function resolveStateBucketWithDefaultAndSource(\n cliBucket: string | undefined,\n region: string\n): Promise<ResolvedStateBucket> {\n // Step 1: explicit value short-circuits the lookup chain.\n const syncResult = resolveStateBucketWithSource(cliBucket);\n if (syncResult) return syncResult;\n\n const logger = getLogger();\n logger.debug('No state bucket specified, resolving default from account...');\n\n const { GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');\n const { S3Client } = await import('@aws-sdk/client-s3');\n const { getAwsClients } = await import('../utils/aws-clients.js');\n const awsClients = getAwsClients();\n const identity = await awsClients.sts.send(new GetCallerIdentityCommand({}));\n const accountId = identity.Account!;\n\n const newName = getDefaultStateBucketName(accountId);\n // TODO(remove-bc-after-1.x): legacy name kept for the backwards-compat read\n // path; remove together with the fallback branch below in PR 99.\n const legacyName = getLegacyStateBucketName(accountId, region);\n\n // Use a region-agnostic client (us-east-1) for the existence checks. S3\n // returns 301 / 404 globally for both names — we don't need the real bucket\n // region to ask whether the bucket exists. The state-bucket S3 client used\n // for actual reads/writes is rebuilt against the bucket's real region via\n // `resolveBucketRegion` later in the flow.\n const probe = new S3Client({ region: 'us-east-1' });\n try {\n const newExists = await bucketExists(probe, newName);\n const legacyExists = await bucketExists(probe, legacyName);\n\n // Step 2 / 3: pick the bucket that actually has state.\n //\n // Three sub-cases when one or both default buckets exist:\n //\n // a. Only new exists → use new (no legacy to consider).\n // b. Only legacy exists → use legacy + deprecation warning, point\n // the user at `cdkd state migrate`.\n // c. Both exist → previously we always picked new. That hid the\n // common upgrade path: legacy bucket from an earlier cdkd\n // version + an empty new bucket left behind by a partial\n // migration / probe / bootstrap. Picking new in that case\n // makes the next deploy think the stack is brand-new and\n // collide with the existing AWS resources. Now we look at\n // whether new actually has state under `cdkd/`. If new is\n // empty AND legacy has state, fall back to legacy with a\n // strong warning telling the user to run migrate.\n if (newExists && legacyExists) {\n const newHasState = await bucketHasAnyState(probe, newName);\n if (!newHasState) {\n const legacyHasState = await bucketHasAnyState(probe, legacyName);\n if (legacyHasState) {\n logger.warn(\n `Both '${newName}' (new default) and '${legacyName}' (legacy default) exist, ` +\n `but the new bucket is empty and the legacy one has state. Reading from legacy. ` +\n `Run \\`cdkd state migrate --region ${region}\\` to copy the state into the new ` +\n `bucket and stop seeing this warning.`\n );\n return { bucket: legacyName, source: 'default-legacy' };\n }\n }\n logger.debug(`State bucket: ${newName}`);\n return { bucket: newName, source: 'default' };\n }\n\n if (newExists) {\n // Logged at debug only — see resolveStateBucketWithDefault doc-comment.\n logger.debug(`State bucket: ${newName}`);\n return { bucket: newName, source: 'default' };\n }\n\n // TODO(remove-bc-after-1.x): drop the legacy fallback branch in PR 99.\n if (legacyExists) {\n logger.warn(\n `Using legacy state bucket name '${legacyName}'. ` +\n `The default has changed to '${newName}'. To migrate, run:\\n\\n` +\n ` cdkd state migrate --region ${region}\\n\\n` +\n `(add --remove-legacy to delete the legacy bucket after a successful copy; ` +\n `legacy support will be dropped in a future release.)`\n );\n return { bucket: legacyName, source: 'default-legacy' };\n }\n\n // Step 4: neither bucket exists.\n throw new Error(\n `No cdkd state bucket found for account ${accountId}. ` +\n `Looked for '${newName}' (current default) and '${legacyName}' (legacy default). ` +\n `Run 'cdkd bootstrap' to create '${newName}'.`\n );\n } finally {\n probe.destroy();\n }\n}\n\n/**\n * Return `true` if the bucket has at least one object under the cdkd state\n * prefix (`cdkd/`). Used to disambiguate \"this bucket holds state\" from\n * \"this bucket exists but is empty\" — the latter happens when a previous\n * `cdkd state migrate` probe / bootstrap left a fresh bucket behind that\n * was never written to.\n *\n * Errors (network, access denied) are treated as \"don't know\" and return\n * `true` — biases toward NOT silently picking the legacy bucket when the\n * new one's state is uncertain. False positives here are harmless (the\n * downstream getState call will surface the real read error); a false\n * negative would silently route to legacy and be confusing.\n */\nasync function bucketHasAnyState(\n client: import('@aws-sdk/client-s3').S3Client,\n bucketName: string\n): Promise<boolean> {\n const { ListObjectsV2Command } = await import('@aws-sdk/client-s3');\n try {\n const resp = await client.send(\n new ListObjectsV2Command({\n Bucket: bucketName,\n Prefix: 'cdkd/',\n MaxKeys: 1,\n })\n );\n return (resp.KeyCount ?? 0) > 0;\n } catch {\n // Conservative: if we can't tell, assume the bucket has state so we\n // don't silently fall through to the legacy bucket.\n return true;\n }\n}\n\n/**\n * Probe whether an S3 bucket exists from this account's perspective.\n *\n * Returns:\n * - `true` for any 2xx (`HeadBucket` succeeded) **or** 301 (the bucket\n * exists, just in a different region — we can still use it because the\n * real region is resolved later by `resolveBucketRegion`).\n * - `true` for 403 (we lack permission to head it, but it exists; let the\n * state-backend produce a more specific error later).\n * - `false` for 404 / `NotFound` / `NoSuchBucket`.\n * - Re-throws anything else so credential / network failures aren't silently\n * swallowed by the lookup chain.\n */\nasync function bucketExists(\n client: import('@aws-sdk/client-s3').S3Client,\n bucketName: string\n): Promise<boolean> {\n const { HeadBucketCommand } = await import('@aws-sdk/client-s3');\n try {\n await client.send(new HeadBucketCommand({ Bucket: bucketName }));\n return true;\n } catch (error) {\n const err = error as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n message?: string;\n };\n const status = err.$metadata?.httpStatusCode;\n if (err.name === 'NotFound' || err.name === 'NoSuchBucket' || status === 404) {\n return false;\n }\n // 301 = bucket exists in a different region (cross-region HEAD redirect).\n // 403 = bucket exists but we lack `s3:ListBucket` — treat as existing so\n // the downstream operation surfaces the real \"access denied\" error.\n if (status === 301 || status === 403) {\n return true;\n }\n // AWS SDK v3 synthetic Unknown error — covers the empty-body 301 redirect\n // case where the SDK fails to parse the status. We can't distinguish from\n // here, so re-throw and let the caller decide.\n throw error;\n }\n}\n","import { existsSync, mkdirSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';\nimport { AppExecutor } from './app-executor.js';\nimport { AssemblyReader, type StackInfo } from './assembly-reader.js';\nimport { ContextStore } from './context-store.js';\nimport { ContextProviderRegistry } from './context-providers/index.js';\nimport type { AssemblyManifest } from '../types/assembly.js';\nimport { loadCdkJson, loadUserCdkJson } from '../cli/config-loader.js';\nimport { getLogger } from '../utils/logger.js';\nimport { SynthesisError } from '../utils/error-handler.js';\n\n/**\n * Synthesis options\n */\nexport interface SynthesisOptions {\n /** CDK app command (e.g., \"node app.ts\") */\n app: string;\n\n /** Output directory for synthesis (default: \"cdk.out\") */\n output?: string;\n\n /** AWS profile to use */\n profile?: string;\n\n /** AWS region */\n region?: string;\n\n /** Context key-value pairs (CLI -c/--context) */\n context?: Record<string, string>;\n}\n\n/**\n * Synthesis result\n */\nexport interface SynthesisResult {\n /** Cloud assembly manifest */\n manifest: AssemblyManifest;\n\n /** Assembly directory (absolute path) */\n assemblyDir: string;\n\n /** All stacks in the assembly */\n stacks: StackInfo[];\n}\n\n/**\n * CDK app synthesizer\n *\n * Replaces @aws-cdk/toolkit-lib with self-implemented:\n * - Subprocess execution of CDK app\n * - Cloud assembly manifest parsing\n * - Context provider loop (missing context → SDK lookup → re-synthesize)\n */\nexport class Synthesizer {\n private logger = getLogger().child('Synthesizer');\n private appExecutor = new AppExecutor();\n private assemblyReader = new AssemblyReader();\n private contextStore = new ContextStore();\n\n /**\n * Synthesize CDK app to cloud assembly\n *\n * Implements the context provider loop:\n * 1. Merge context (cdk.json context + cdk.context.json + CLI -c)\n * 2. Execute CDK app subprocess\n * 3. Read manifest.json\n * 4. If missing context → resolve via providers → save to cdk.context.json → re-execute\n * 5. Return assembly with stacks\n */\n async synthesize(options: SynthesisOptions): Promise<SynthesisResult> {\n // CDK CLI compatibility: if --app points at an existing directory, treat it\n // as a pre-synthesized cloud assembly and skip subprocess execution.\n // See aws-cdk/lib/cxapp/exec.ts: \"bypass 'synth' if app points to a cloud assembly\".\n const appPath = resolve(options.app);\n if (existsSync(appPath) && statSync(appPath).isDirectory()) {\n this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);\n const manifest = this.assemblyReader.readManifest(appPath);\n const stacks = this.assemblyReader.getAllStacks(appPath, manifest);\n this.logger.debug(`Loaded ${stacks.length} stack(s) from pre-synthesized assembly`);\n return { manifest, assemblyDir: appPath, stacks };\n }\n\n const outputDir = resolve(options.output || 'cdk.out');\n\n // Ensure output directory exists\n mkdirSync(outputDir, { recursive: true });\n\n // Load static context (doesn't change during loop)\n // Priority: defaults < ~/.cdk.json < cdk.json < cdk.context.json < CLI -c\n const userCdkJson = loadUserCdkJson();\n const userContext = (userCdkJson?.context as Record<string, unknown>) ?? {};\n const cdkJson = loadCdkJson();\n const cdkJsonContext = (cdkJson?.context as Record<string, unknown>) ?? {};\n const cliContext = (options.context as Record<string, unknown>) ?? {};\n\n // CDK CLI injects these context values by default for framework compatibility\n const cdkDefaults: Record<string, unknown> = {\n 'aws:cdk:enable-path-metadata': true,\n 'aws:cdk:enable-asset-metadata': true,\n 'aws:cdk:version-reporting': true,\n 'aws:cdk:bundling-stacks': ['**'],\n };\n\n // Resolve AWS account/region for context passing\n const region = options.region || process.env['AWS_REGION'] || process.env['AWS_DEFAULT_REGION'];\n let accountId: string | undefined;\n try {\n const stsClient = new STSClient({ ...(region && { region }) });\n const identity = await stsClient.send(new GetCallerIdentityCommand({}));\n accountId = identity.Account;\n stsClient.destroy();\n } catch {\n this.logger.debug('Could not resolve AWS account ID via STS');\n }\n\n // Context provider loop\n let previousMissingKeys: Set<string> | undefined;\n const contextProviderRegistry = new ContextProviderRegistry({\n ...(region && { region }),\n ...(options.profile && { profile: options.profile }),\n });\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n // Load cdk.context.json (re-read each iteration — providers may have updated it)\n const cdkContextJson = this.contextStore.load();\n\n // Merge context: defaults < ~/.cdk.json < cdk.json < cdk.context.json < CLI -c (CLI wins)\n const mergedContext: Record<string, unknown> = {\n ...cdkDefaults,\n ...userContext,\n ...cdkJsonContext,\n ...cdkContextJson,\n ...cliContext,\n };\n\n // Execute CDK app\n this.logger.debug('Executing CDK app...');\n await this.appExecutor.execute({\n app: options.app,\n outputDir,\n context: mergedContext,\n ...(region && { region }),\n ...(accountId && { accountId }),\n });\n\n // Read manifest\n const manifest = this.assemblyReader.readManifest(outputDir);\n\n // Check for missing context\n if (!manifest.missing || manifest.missing.length === 0) {\n // Synthesis complete\n const stacks = this.assemblyReader.getAllStacks(outputDir, manifest);\n this.logger.debug(`Synthesis complete: ${stacks.length} stack(s)`);\n\n return { manifest, assemblyDir: outputDir, stacks };\n }\n\n // Missing context detected\n const missingKeys = new Set(manifest.missing.map((m) => m.key));\n this.logger.debug(`Missing context: ${manifest.missing.length} value(s)`);\n\n // Check for no progress (same missing keys as last iteration)\n if (previousMissingKeys && setsEqual(missingKeys, previousMissingKeys)) {\n throw new SynthesisError(\n 'Context resolution made no progress. ' +\n `Missing context keys: ${[...missingKeys].join(', ')}. ` +\n 'Ensure cdk.context.json is correctly configured or required AWS permissions are granted.'\n );\n }\n previousMissingKeys = missingKeys;\n\n // Resolve missing context via providers\n this.logger.info('Resolving missing context...');\n const resolved = await contextProviderRegistry.resolve(manifest.missing);\n\n // Save resolved values to cdk.context.json\n this.contextStore.save(resolved);\n\n // Loop: re-execute CDK app with updated context\n this.logger.debug('Re-synthesizing with resolved context...');\n }\n }\n\n /**\n * List stack names in CDK app\n */\n async listStacks(options: SynthesisOptions): Promise<string[]> {\n const result = await this.synthesize(options);\n return result.stacks.map((s) => s.stackName);\n }\n}\n\n/**\n * Check if two sets contain the same elements\n */\nfunction setsEqual<T>(a: Set<T>, b: Set<T>): boolean {\n if (a.size !== b.size) return false;\n for (const item of a) {\n if (!b.has(item)) return false;\n }\n return true;\n}\n","import { createReadStream, statSync } from 'node:fs';\nimport { join, basename } from 'node:path';\nimport { S3Client, HeadObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';\nimport type { FileAsset } from '../types/assets.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Publishes file assets to S3\n *\n * Handles:\n * - Placeholder resolution (${AWS::AccountId}, ${AWS::Region})\n * - Existence check (skip if already uploaded)\n * - ZIP packaging for directory assets\n * - Direct file upload for single files\n */\nexport class FileAssetPublisher {\n private logger = getLogger().child('FileAssetPublisher');\n\n /**\n * Publish a file asset to S3\n *\n * @param assetHash Asset hash (ID)\n * @param asset File asset definition\n * @param cdkOutputDir CDK output directory (cdk.out)\n * @param accountId AWS account ID\n * @param region AWS region\n * @param profile AWS profile (optional)\n */\n async publish(\n assetHash: string,\n asset: FileAsset,\n cdkOutputDir: string,\n accountId: string,\n region: string,\n _profile?: string\n ): Promise<void> {\n // Process each destination\n for (const [, dest] of Object.entries(asset.destinations)) {\n const bucketName = this.resolvePlaceholders(dest.bucketName, accountId, region);\n const objectKey = this.resolvePlaceholders(dest.objectKey, accountId, region);\n const destRegion = dest.region\n ? this.resolvePlaceholders(dest.region, accountId, region)\n : region;\n\n this.logger.debug(\n `Publishing file asset ${asset.displayName || assetHash} → s3://${bucketName}/${objectKey}`\n );\n\n const client = new S3Client({\n region: destRegion,\n });\n\n try {\n // Check if already exists\n if (await this.objectExists(client, bucketName, objectKey)) {\n this.logger.debug(`Asset already exists, skipping: s3://${bucketName}/${objectKey}`);\n continue;\n }\n\n // Determine source path\n const sourcePath = join(cdkOutputDir, asset.source.path);\n\n if (asset.source.packaging === 'zip') {\n // ZIP packaging: create zip archive and upload\n await this.uploadZip(client, sourcePath, bucketName, objectKey);\n } else {\n // Direct file upload\n await this.uploadFile(client, sourcePath, bucketName, objectKey);\n }\n\n this.logger.debug(`✅ Published: s3://${bucketName}/${objectKey}`);\n } finally {\n client.destroy();\n }\n }\n }\n\n /**\n * Check if an S3 object exists\n */\n private async objectExists(client: S3Client, bucket: string, key: string): Promise<boolean> {\n try {\n await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n return true;\n } catch (error) {\n const err = error as {\n name?: string;\n message?: string;\n $metadata?: { httpStatusCode?: number };\n };\n if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) {\n return false;\n }\n // Provide helpful error for common issues\n const statusCode = err.$metadata?.httpStatusCode;\n if (statusCode === 301 || err.name === 'PermanentRedirect') {\n throw new Error(\n `S3 bucket '${bucket}' is in a different region. ` +\n `Use --region to specify the correct region, or check asset manifest destination.`\n );\n }\n throw new Error(\n `Failed to check S3 object s3://${bucket}/${key}: ${err.name || 'UnknownError'}: ${err.message || String(error)}`\n );\n }\n }\n\n /**\n * Upload a single file to S3\n */\n private async uploadFile(\n client: S3Client,\n filePath: string,\n bucket: string,\n key: string\n ): Promise<void> {\n const stat = statSync(filePath);\n const stream = createReadStream(filePath);\n\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n Body: stream,\n ContentLength: stat.size,\n })\n );\n }\n\n /**\n * Create ZIP archive and upload to S3\n */\n private async uploadZip(\n client: S3Client,\n dirPath: string,\n bucket: string,\n key: string\n ): Promise<void> {\n const archiver = await import('archiver');\n\n // Collect all archive data into a buffer before uploading\n const body = await new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n const archive = archiver.default('zip', { zlib: { level: 9 } });\n\n archive.on('data', (chunk: Buffer) => chunks.push(chunk));\n archive.on('end', () => resolve(Buffer.concat(chunks)));\n archive.on('error', reject);\n\n // Check if dirPath is a file or directory\n const stat = statSync(dirPath);\n if (stat.isDirectory()) {\n archive.directory(dirPath, false);\n } else {\n archive.file(dirPath, { name: basename(dirPath) });\n }\n\n void archive.finalize();\n });\n\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n Body: body,\n ContentLength: body.length,\n })\n );\n }\n\n /**\n * Replace placeholders in destination values\n */\n private resolvePlaceholders(\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","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { DockerImageAssetSource } from '../types/assets.js';\nimport { getLogger } from '../utils/logger.js';\n\nconst execFileAsync = promisify(execFile);\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\n * via `cdkd local invoke`).\n *\n * Invariants preserved across the two callers:\n * - `maxBuffer: 50 * 1024 * 1024` so a verbose `docker build` log doesn't\n * blow up `execFile`'s default 1 MB buffer.\n * - Build args iterate in the order `Object.entries(...)` returns them so\n * the resulting layer cache is stable across runs (any reordering would\n * bust caches for both the publisher and local invoke).\n * - Errors carry the captured stderr so the user can re-run `docker build`\n * directly to debug. The error class is parameterized: each consumer\n * wraps the failure with its own typed error (`AssetError` for the\n * publisher, `LocalInvokeBuildError` for local invoke) so the existing\n * error-handling chain on each side keeps working unchanged.\n *\n * `platform` is new in PR 5: container Lambdas declare `Architectures:\n * [x86_64]` (default) or `[arm64]`, and the local-invoke caller MUST pass the\n * matching `linux/amd64` / `linux/arm64` so the built image can run on the\n * developer's host (which may have the opposite arch). The publisher caller\n * defaults to `undefined` for backward compatibility — passing through is\n * the no-op, the user's local docker default arch picks up.\n */\n\n/**\n * Build a Docker image from a CDK asset's source description.\n *\n * @param asset The `DockerImageAsset` entry from the cdk asset\n * manifest (carries `directory`, `dockerFile`, build args,\n * target, outputs).\n * @param cdkOutDir Absolute path to the CDK output directory (`cdk.out`).\n * Used to resolve `asset.source.directory` to a real\n * build context on disk.\n * @param tag Local image tag to apply (`-t`). The caller chooses a\n * deterministic tag so subsequent runs hit Docker's\n * layer cache (publisher uses `cdkd-asset-<hash>`;\n * local-invoke uses `cdkd-local-invoke-<hash>`).\n * @param platform Optional `--platform` value (e.g. `linux/amd64`,\n * `linux/arm64`). When `undefined` the flag is omitted\n * and Docker uses its default platform.\n * @param wrapError Function the caller provides to wrap the underlying\n * `docker build` failure in a typed error specific to\n * its call site.\n * @throws Whatever `wrapError` returns when `docker build` exits non-zero.\n */\nexport async function buildDockerImage(\n asset: { source: DockerImageAssetSource },\n cdkOutDir: string,\n tag: string,\n options: {\n platform?: string;\n wrapError: (stderr: string) => Error;\n }\n): Promise<void> {\n const logger = getLogger().child('docker-build');\n const args: string[] = ['build', '-t', tag];\n\n if (options.platform) {\n args.push('--platform', options.platform);\n }\n\n // Dockerfile\n if (asset.source.dockerFile) {\n args.push('-f', asset.source.dockerFile);\n }\n\n // Build args (order preserved per Object.entries — load-bearing for cache\n // reproducibility across both callers).\n if (asset.source.dockerBuildArgs) {\n for (const [key, value] of Object.entries(asset.source.dockerBuildArgs)) {\n args.push('--build-arg', `${key}=${value}`);\n }\n }\n\n // Build target\n if (asset.source.dockerBuildTarget) {\n args.push('--target', asset.source.dockerBuildTarget);\n }\n\n // Build outputs\n if (asset.source.dockerOutputs) {\n for (const output of asset.source.dockerOutputs) {\n args.push('--output', output);\n }\n }\n\n // Context directory\n const contextDir = `${cdkOutDir}/${asset.source.directory}`;\n args.push(contextDir);\n\n logger.debug(`docker ${args.join(' ')}`);\n\n try {\n await execFileAsync('docker', args, {\n maxBuffer: 50 * 1024 * 1024, // 50MB for build output\n });\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n const stderr = err.stderr || err.message || String(error);\n throw options.wrapError(stderr);\n }\n}\n","import { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport {\n ECRClient,\n GetAuthorizationTokenCommand,\n DescribeImagesCommand,\n} from '@aws-sdk/client-ecr';\nimport type { DockerImageAsset } from '../types/assets.js';\nimport { getLogger } from '../utils/logger.js';\nimport { AssetError } from '../utils/error-handler.js';\nimport { buildDockerImage } from './docker-build.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Publishes Docker image assets to ECR\n *\n * Handles:\n * - Placeholder resolution\n * - Existence check (skip if already pushed)\n * - docker build with Dockerfile, build args, target\n * - ECR authentication\n * - docker tag + docker push\n */\nexport class DockerAssetPublisher {\n private logger = getLogger().child('DockerAssetPublisher');\n\n /**\n * Publish a Docker image asset to ECR\n */\n async publish(\n assetHash: string,\n asset: DockerImageAsset,\n cdkOutputDir: string,\n accountId: string,\n region: string\n ): Promise<void> {\n for (const [, dest] of Object.entries(asset.destinations)) {\n const repositoryName = this.resolvePlaceholders(dest.repositoryName, accountId, region);\n const imageTag = this.resolvePlaceholders(dest.imageTag, accountId, region);\n const destRegion = dest.region\n ? this.resolvePlaceholders(dest.region, accountId, region)\n : region;\n\n const ecrUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n\n this.logger.debug(`Publishing Docker image ${asset.displayName || assetHash} → ${ecrUri}`);\n\n const client = new ECRClient({ region: destRegion });\n\n try {\n // Check if image already exists\n if (await this.imageExists(client, repositoryName, imageTag)) {\n this.logger.debug(`Image already exists, skipping: ${ecrUri}`);\n continue;\n }\n\n // Build Docker image\n const localTag = `cdkd-asset-${assetHash}`;\n await this.buildImage(asset, cdkOutputDir, localTag);\n\n // Authenticate with ECR\n await this.ecrLogin(client, accountId, destRegion);\n\n // Tag and push\n const fullUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n await this.tagImage(localTag, fullUri);\n await this.pushImage(fullUri);\n\n this.logger.debug(`✅ Published: ${ecrUri}`);\n } finally {\n client.destroy();\n }\n }\n }\n\n /**\n * Build a Docker image (public, used by WorkGraph asset-build nodes)\n */\n async build(asset: DockerImageAsset, cdkOutputDir: string, localTag: string): Promise<void> {\n await this.buildImage(asset, cdkOutputDir, localTag);\n }\n\n /**\n * Push a pre-built Docker image to ECR (public, used by WorkGraph asset-publish nodes)\n */\n async push(\n asset: DockerImageAsset,\n accountId: string,\n region: string,\n localTag: string\n ): Promise<void> {\n for (const [, dest] of Object.entries(asset.destinations)) {\n const repositoryName = this.resolvePlaceholders(dest.repositoryName, accountId, region);\n const imageTag = this.resolvePlaceholders(dest.imageTag, accountId, region);\n const destRegion = dest.region\n ? this.resolvePlaceholders(dest.region, accountId, region)\n : region;\n\n const ecrUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n\n const client = new ECRClient({ region: destRegion });\n\n try {\n if (await this.imageExists(client, repositoryName, imageTag)) {\n this.logger.debug(`Image already exists, skipping: ${ecrUri}`);\n continue;\n }\n\n await this.ecrLogin(client, accountId, destRegion);\n\n const fullUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n await this.tagImage(localTag, fullUri);\n await this.pushImage(fullUri);\n\n this.logger.debug(`✅ Published: ${ecrUri}`);\n } finally {\n client.destroy();\n }\n }\n }\n\n /**\n * Check if image exists in ECR\n */\n private async imageExists(\n client: ECRClient,\n repositoryName: string,\n imageTag: string\n ): Promise<boolean> {\n try {\n const response = await client.send(\n new DescribeImagesCommand({\n repositoryName,\n imageIds: [{ imageTag }],\n })\n );\n return (response.imageDetails?.length ?? 0) > 0;\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ImageNotFoundException' || err.name === 'RepositoryNotFoundException') {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Build Docker image — delegates to the shared `buildDockerImage`\n * helper so this code path stays in sync with `cdkd local invoke`'s\n * container-Lambda build path. `--platform` is currently not threaded\n * through here (publish-assets has no Architectures hint to consult);\n * a follow-up can lift this once the asset manifest carries a\n * platform field.\n */\n private async buildImage(\n asset: DockerImageAsset,\n cdkOutputDir: string,\n tag: string\n ): Promise<void> {\n await buildDockerImage(asset, cdkOutputDir, tag, {\n wrapError: (stderr) => new AssetError(`Docker build failed: ${stderr}`),\n });\n }\n\n /**\n * Authenticate with ECR\n */\n private async ecrLogin(client: ECRClient, accountId: string, region: string): Promise<void> {\n const response = await client.send(new GetAuthorizationTokenCommand({}));\n const authData = response.authorizationData?.[0];\n\n if (!authData?.authorizationToken) {\n throw new AssetError('Failed to get ECR authorization token');\n }\n\n const token = Buffer.from(authData.authorizationToken, 'base64').toString();\n const [username, password] = token.split(':');\n const endpoint =\n authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.amazonaws.com`;\n\n await new Promise<void>((resolve, reject) => {\n const proc = spawn(\n 'docker',\n ['login', '--username', username!, '--password-stdin', endpoint],\n {\n stdio: ['pipe', 'pipe', 'pipe'],\n }\n );\n\n let stderr = '';\n proc.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new AssetError(`ECR login failed: ${stderr.trim()}`));\n }\n });\n\n proc.on('error', (err) => {\n reject(new AssetError(`ECR login failed: ${err.message}`));\n });\n\n // Write password to stdin and close\n proc.stdin?.write(password);\n proc.stdin?.end();\n });\n }\n\n /**\n * Tag Docker image\n */\n private async tagImage(source: string, target: string): Promise<void> {\n await execFileAsync('docker', ['tag', source, target]);\n }\n\n /**\n * Push Docker image\n */\n private async pushImage(uri: string): Promise<void> {\n this.logger.debug(`Pushing: ${uri}`);\n try {\n await execFileAsync('docker', ['push', uri], {\n maxBuffer: 50 * 1024 * 1024,\n });\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n throw new AssetError(`Docker push failed: ${err.stderr || err.message || String(error)}`);\n }\n }\n\n /**\n * Replace placeholders in destination values\n */\n private resolvePlaceholders(\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","import { getLogger } from '../utils/logger.js';\n\n/**\n * Node types in the work graph\n */\nexport type WorkNodeType = 'asset-build' | 'asset-publish' | 'stack';\n\n/**\n * Node states\n */\nexport type NodeState = 'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'skipped';\n\n/**\n * A node in the work graph\n */\nexport interface WorkNode {\n id: string;\n type: WorkNodeType;\n dependencies: Set<string>;\n state: NodeState;\n /** Custom data attached to this node */\n data: unknown;\n}\n\n/**\n * Concurrency limits per node type\n */\nexport interface WorkGraphConcurrency {\n 'asset-build': number;\n 'asset-publish': number;\n stack: number;\n}\n\n/**\n * Work graph for orchestrating asset building, publishing and stack deployments.\n *\n * Manages a DAG of nodes with dependencies, executing them in parallel\n * with per-type concurrency limits. Nodes become ready when all their\n * dependencies are completed.\n *\n * Node types:\n * - asset-build: Docker image build (CPU/memory bound)\n * - asset-publish: S3 upload or ECR push (I/O bound)\n * - stack: Stack deployment (depends on its asset nodes)\n *\n * Dependencies:\n * - File assets: asset-publish → stack\n * - Docker assets: asset-build → asset-publish → stack\n * - Inter-stack: stack → stack (CDK dependency order)\n */\nexport class WorkGraph {\n private nodes = new Map<string, WorkNode>();\n private logger = getLogger().child('WorkGraph');\n\n addNode(node: WorkNode): void {\n this.nodes.set(node.id, node);\n }\n\n /**\n * Execute all nodes in the graph with bounded concurrency per type.\n */\n async execute(\n concurrency: WorkGraphConcurrency,\n fn: (node: WorkNode) => Promise<void>\n ): Promise<void> {\n const active: Record<WorkNodeType, number> = { 'asset-build': 0, 'asset-publish': 0, stack: 0 };\n const errors: Array<{ nodeId: string; error: unknown }> = [];\n\n return new Promise<void>((resolve, reject) => {\n const dispatch = (): void => {\n // Find ready nodes: pending with all dependencies completed\n const ready: WorkNode[] = [];\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const depsReady = [...node.dependencies].every((depId) => {\n const dep = this.nodes.get(depId);\n return dep && dep.state === 'completed';\n });\n if (depsReady) {\n ready.push(node);\n }\n }\n\n // Skip nodes with failed dependencies\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const hasFailedDep = [...node.dependencies].some((depId) => {\n const dep = this.nodes.get(depId);\n return dep && (dep.state === 'failed' || dep.state === 'skipped');\n });\n if (hasFailedDep) {\n node.state = 'skipped';\n this.logger.debug(`Skipped ${node.id}: dependency failed`);\n }\n }\n\n // Start eligible nodes\n for (const node of ready) {\n if (active[node.type] >= concurrency[node.type]) continue;\n\n node.state = 'running';\n active[node.type]++;\n\n fn(node)\n .then(() => {\n node.state = 'completed';\n })\n .catch((error) => {\n node.state = 'failed';\n errors.push({ nodeId: node.id, error });\n this.logger.error(\n `Failed: ${node.id}: ${error instanceof Error ? error.message : String(error)}`\n );\n })\n .finally(() => {\n active[node.type]--;\n dispatch(); // Re-evaluate after each completion\n });\n }\n\n // Check termination\n const totalActive = active['asset-build'] + active['asset-publish'] + active['stack'];\n if (totalActive === 0) {\n const pending = [...this.nodes.values()].filter(\n (n) => n.state === 'pending' || n.state === 'queued'\n );\n\n if (pending.length > 0) {\n reject(\n new Error(\n `Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies`\n )\n );\n return;\n }\n\n if (errors.length > 0) {\n const skippedCount = [...this.nodes.values()].filter(\n (n) => n.state === 'skipped'\n ).length;\n const msg = errors\n .map(\n (e) =>\n ` - ${e.nodeId}: ${e.error instanceof Error ? e.error.message : String(e.error)}`\n )\n .join('\\n');\n reject(\n new Error(\n `${errors.length} node(s) failed${skippedCount > 0 ? `, ${skippedCount} skipped` : ''}:\\n${msg}`\n )\n );\n return;\n }\n\n resolve();\n }\n };\n\n dispatch();\n });\n }\n\n /**\n * Get summary of node counts by type\n */\n summary(): Record<WorkNodeType, number> {\n const counts: Record<WorkNodeType, number> = { 'asset-build': 0, 'asset-publish': 0, stack: 0 };\n for (const node of this.nodes.values()) {\n counts[node.type]++;\n }\n return counts;\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 { readFileSync } from 'node:fs';\nimport { FileAssetPublisher } from './file-asset-publisher.js';\nimport { DockerAssetPublisher } from './docker-asset-publisher.js';\nimport type { AssetManifest, FileAsset, DockerImageAsset } from '../types/assets.js';\nimport { WorkGraph, type WorkNode } from '../deployment/work-graph.js';\nimport { getLogger } from '../utils/logger.js';\nimport { AssetError } from '../utils/error-handler.js';\nimport { stringifyValue } from '../utils/stringify.js';\n\n/**\n * Data attached to a file asset-publish node\n */\nexport interface FileAssetNodeData {\n kind: 'file';\n hash: string;\n asset: FileAsset;\n cdkOutputDir: string;\n accountId: string;\n region: string;\n profile?: string;\n}\n\n/**\n * Data attached to a Docker asset-build node\n */\nexport interface DockerBuildNodeData {\n kind: 'docker-build';\n hash: string;\n asset: DockerImageAsset;\n cdkOutputDir: string;\n localTag: string;\n}\n\n/**\n * Data attached to a Docker asset-publish node\n */\nexport interface DockerPublishNodeData {\n kind: 'docker-publish';\n asset: DockerImageAsset;\n accountId: string;\n region: string;\n localTag: string;\n}\n\nexport type AssetNodeData = FileAssetNodeData | DockerBuildNodeData | DockerPublishNodeData;\n\n/**\n * Asset publishing options\n */\nexport interface AssetPublisherOptions {\n /** AWS profile to use */\n profile?: string;\n\n /** AWS region */\n region?: string;\n\n /** AWS account ID */\n accountId?: string;\n\n /** Concurrency for asset publishing (S3 uploads + ECR push). Default: 8 */\n assetPublishConcurrency?: number;\n\n /** Concurrency for Docker image builds. Default: 4 */\n imageBuildConcurrency?: number;\n}\n\n/**\n * Asset publisher\n *\n * Orchestrates file and Docker image asset publishing via WorkGraph.\n * - File assets: single asset-publish node (S3 upload)\n * - Docker assets: asset-build node → asset-publish node (build then push)\n */\nexport class AssetPublisher {\n private logger = getLogger().child('AssetPublisher');\n private filePublisher = new FileAssetPublisher();\n private dockerPublisher = new DockerAssetPublisher();\n\n /**\n * Add asset nodes from a manifest to a WorkGraph.\n * Returns the node IDs that stack deploy should depend on.\n */\n addAssetsToGraph(\n graph: WorkGraph,\n manifestPath: string,\n options: { accountId: string; region: string; profile?: string; nodePrefix?: string }\n ): string[] {\n const content = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as AssetManifest;\n const cdkOutputDir = manifestPath.replace(/\\/[^/]+$/, '');\n const prefix = options.nodePrefix || '';\n const nodeIds: string[] = [];\n\n // File assets: single publish node\n const fileAssets = Object.entries(manifest.files || {}).filter(\n ([, asset]) =>\n !asset.source.path.endsWith('.json') && !asset.source.path.endsWith('.template.json')\n );\n for (const [hash, asset] of fileAssets) {\n const nodeId = `asset-publish:${prefix}file:${hash}`;\n graph.addNode({\n id: nodeId,\n type: 'asset-publish',\n dependencies: new Set(),\n state: 'pending',\n data: {\n kind: 'file',\n hash,\n asset,\n cdkOutputDir,\n accountId: options.accountId,\n region: options.region,\n ...(options.profile && { profile: options.profile }),\n } satisfies FileAssetNodeData,\n });\n nodeIds.push(nodeId);\n }\n\n // Docker assets: build node → publish node\n for (const [hash, asset] of Object.entries(manifest.dockerImages || {})) {\n const localTag = `cdkd-asset-${hash}`;\n const buildNodeId = `asset-build:${prefix}docker:${hash}`;\n const publishNodeId = `asset-publish:${prefix}docker:${hash}`;\n\n graph.addNode({\n id: buildNodeId,\n type: 'asset-build',\n dependencies: new Set(),\n state: 'pending',\n data: {\n kind: 'docker-build',\n hash,\n asset,\n cdkOutputDir,\n localTag,\n } satisfies DockerBuildNodeData,\n });\n\n graph.addNode({\n id: publishNodeId,\n type: 'asset-publish',\n dependencies: new Set([buildNodeId]),\n state: 'pending',\n data: {\n kind: 'docker-publish',\n asset,\n accountId: options.accountId,\n region: options.region,\n localTag,\n } satisfies DockerPublishNodeData,\n });\n\n // Stack depends on the publish node (not build)\n nodeIds.push(publishNodeId);\n }\n\n this.logger.debug(\n `Added ${fileAssets.length} file + ${Object.keys(manifest.dockerImages || {}).length} docker asset(s) to graph`\n );\n\n return nodeIds;\n }\n\n /**\n * Execute an asset node (build or publish)\n */\n async executeNode(node: WorkNode): Promise<void> {\n const data = node.data as AssetNodeData;\n\n if (data.kind === 'file') {\n await this.filePublisher.publish(\n data.hash,\n data.asset,\n data.cdkOutputDir,\n data.accountId,\n data.region,\n data.profile\n );\n } else if (data.kind === 'docker-build') {\n await this.dockerPublisher.build(data.asset, data.cdkOutputDir, data.localTag);\n } else if (data.kind === 'docker-publish') {\n await this.dockerPublisher.push(data.asset, data.accountId, data.region, data.localTag);\n }\n\n this.logger.debug(`✅ ${node.id}`);\n }\n\n /**\n * Publish assets from manifest file (standalone, uses WorkGraph internally)\n */\n async publishFromManifest(\n manifestPath: string,\n options: AssetPublisherOptions = {}\n ): Promise<void> {\n try {\n this.logger.debug('Loading asset manifest:', manifestPath);\n\n const region = options.region || process.env['AWS_REGION'] || 'us-east-1';\n let accountId = options.accountId;\n\n if (!accountId) {\n const { STSClient, GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');\n const stsClient = new STSClient({ region });\n const identity = await stsClient.send(new GetCallerIdentityCommand({}));\n accountId = identity.Account!;\n stsClient.destroy();\n }\n\n const graph = new WorkGraph();\n const nodeIds = this.addAssetsToGraph(graph, manifestPath, {\n accountId,\n region,\n ...(options.profile && { profile: options.profile }),\n });\n\n if (nodeIds.length === 0) {\n this.logger.debug('No assets to publish');\n return;\n }\n\n await graph.execute(\n {\n 'asset-build': options.imageBuildConcurrency ?? 4,\n 'asset-publish': options.assetPublishConcurrency ?? 8,\n stack: 0,\n },\n (node) => this.executeNode(node)\n );\n\n this.logger.debug('✅ All assets published successfully');\n } catch (error) {\n if (error instanceof AssetError) {\n throw error;\n }\n const err = error as Record<string, unknown>;\n const message = stringifyValue(err['message'] || err['name'] || error);\n const code = stringifyValue(err['Code'] || err['code'] || err['name'] || '');\n const detail = code ? `${code}: ${message}` : message;\n throw new AssetError(\n `Asset publishing failed: ${detail}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Check if assets need to be published\n */\n hasAssets(manifestPath: string): boolean {\n try {\n const content = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as AssetManifest;\n const fileCount = Object.keys(manifest.files || {}).length;\n const dockerCount = Object.keys(manifest.dockerImages || {}).length;\n return fileCount + dockerCount > 0;\n } catch {\n this.logger.warn('Failed to check assets');\n return false;\n }\n }\n}\n","/**\n * Schema versions for cdkd state.json.\n *\n * - 1 — legacy layout: `s3://{bucket}/cdkd/{stackName}/state.json` (pre PR 1).\n * - 2 — region-prefixed layout: `s3://{bucket}/cdkd/{stackName}/{region}/state.json`.\n * - 3 — adds `ResourceState.observedProperties` (AWS-current snapshot\n * captured at deploy/import time, used as the drift comparator's\n * baseline). Layout is the same as v2; only the resource-level shape\n * grew. v2 readers see v3 as `version: 3` and fail clearly.\n * - 4 — adds `StackState.imports` (the set of `Fn::ImportValue` references\n * this stack resolved during its last deploy). Consumed by\n * `cdkd destroy` to refuse deleting a producer while a consumer still\n * references its outputs (strong reference, matches CloudFormation).\n * Layout is the same as v3; only the stack-level shape grew. v3\n * readers see v4 as `version: 4` and fail clearly.\n * - 5 — adds `ResourceState.deletionPolicy` and `updateReplacePolicy`, the\n * CloudFormation template attributes recorded at deploy time. cdkd\n * compares these against the next deploy's template to detect\n * attribute-only changes (e.g. `RemovalPolicy.DESTROY` removed →\n * `DeletionPolicy: Retain` now in template), which previously fell\n * through DiffCalculator as `No changes detected`. Layout is the same\n * as v4; only the resource-level shape grew. v4 readers see v5 as\n * `version: 5` and fail clearly.\n *\n * cdkd readers handle every prior version. Writers always emit\n * `STATE_SCHEMA_VERSION_CURRENT`. An older cdkd binary that only knows an\n * earlier version will fail with a clear error when it encounters a higher\n * version, rather than silently mishandling the new format.\n */\nexport type StateSchemaVersion = 1 | 2 | 3 | 4 | 5;\nexport const STATE_SCHEMA_VERSION_LEGACY: StateSchemaVersion = 1;\nexport const STATE_SCHEMA_VERSION_CURRENT: StateSchemaVersion = 5;\n\n/**\n * Every schema version this binary can read. Writers always emit\n * `STATE_SCHEMA_VERSION_CURRENT`; older versions are accepted for\n * forward-migration, and an unknown / future version triggers an explicit\n * \"upgrade cdkd\" error in the parser.\n */\nexport const STATE_SCHEMA_VERSIONS_READABLE: readonly StateSchemaVersion[] = [1, 2, 3, 4, 5];\n\n/**\n * One `Fn::ImportValue` reference recorded during a consumer stack's\n * deploy. Persisted in `StackState.imports` so `cdkd destroy` can refuse\n * to delete the producer while the consumer still references its outputs\n * (strong reference, matches CloudFormation behavior).\n *\n * Only `Fn::ImportValue` populates this — `Fn::GetStackOutput` is a weak\n * reference by design (cdkd-specific) and intentionally does NOT record\n * an entry here so the producer stays deletable independently of consumers.\n */\nexport interface StateImportEntry {\n /** The producer stack whose Output `Export.Name` was imported. */\n sourceStack: string;\n /**\n * The producer's region. Required so destroy-time strong-ref checks\n * can scan the producer's exact `state.json` key (cdkd state is keyed\n * by `(stackName, region)` since schema v2).\n */\n sourceRegion: string;\n /** The CloudFormation Output `Export.Name` that was imported. */\n exportName: string;\n}\n\n/**\n * Stack state stored in S3\n */\nexport interface StackState {\n /**\n * Schema version. `1` is the legacy unversioned-key layout, `2` is the\n * region-prefixed layout. New writes always use the current version.\n */\n version: StateSchemaVersion;\n\n /** Stack name */\n stackName: string;\n\n /**\n * Target region for this stack. Required on `version: 2` since the region\n * is part of the S3 key. Optional on `version: 1` for backwards compat.\n */\n region?: string;\n\n /** Resources in the stack */\n resources: Record<string, ResourceState>;\n\n /** Stack outputs (values can be any type) */\n outputs: Record<string, unknown>;\n\n /**\n * `Fn::ImportValue` references this stack resolved during its last\n * successful deploy. Populated on schema v4+; absent (or undefined)\n * on state written by an older cdkd binary, in which case the\n * destroy-time strong-reference check degrades gracefully (no\n * recorded imports = no consumers known = destroy proceeds). The\n * next deploy of an upgraded stack repopulates the field.\n */\n imports?: StateImportEntry[];\n\n /** Last modification timestamp (Unix milliseconds) */\n lastModified: number;\n}\n\n/**\n * Individual resource state\n */\nexport interface ResourceState {\n /** Physical resource ID (ARN, name, etc.) */\n physicalId: string;\n\n /** CloudFormation resource type (e.g., AWS::Lambda::Function) */\n resourceType: string;\n\n /** Resource properties */\n properties: Record<string, unknown>;\n\n /**\n * AWS-current snapshot of this resource's properties as returned by\n * `provider.readCurrentState` immediately after a successful create /\n * update / import. Used as the drift comparator's baseline (instead of\n * `properties`) so console-side changes to keys the user did not\n * template still surface as drift.\n *\n * Optional for backwards compatibility — resources written by an older\n * cdkd binary (v2 state, or v3 state on a provider that does not\n * implement `readCurrentState`) keep this field undefined; the drift\n * command falls back to comparing against `properties` in that case.\n */\n observedProperties?: Record<string, unknown>;\n\n /** Resource attributes for Fn::GetAtt resolution */\n attributes?: Record<string, unknown>;\n\n /** Resource dependencies (logical IDs) for proper deletion order */\n dependencies?: string[];\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n\n /**\n * CloudFormation `DeletionPolicy` attribute recorded at deploy time\n * (schema v5+). Compared against the template on the next deploy so an\n * attribute-only change (e.g. `RemovalPolicy.DESTROY` removed →\n * `DeletionPolicy: Retain`) is surfaced as a diff instead of silently\n * being marked `No changes`. Optional for backwards compatibility — v4\n * state writes leave this undefined; the diff comparator treats\n * `undefined` as \"no attribute recorded\" rather than \"Delete\" so the\n * first post-upgrade deploy only fires the diff when the template\n * actually carries the attribute.\n *\n * The `| undefined` is explicit (vs bare `?:`) so a state-update site\n * can spread `{ ...current, deletionPolicy: undefined }` to clear a\n * previously-recorded value when the user removes the attribute from\n * their CDK code; under `exactOptionalPropertyTypes: true` a bare `?:`\n * would reject the literal-undefined assignment.\n */\n deletionPolicy?: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n\n /**\n * CloudFormation `UpdateReplacePolicy` attribute recorded at deploy time\n * (schema v5+). Same semantics as `deletionPolicy` above.\n */\n updateReplacePolicy?: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n}\n\n/**\n * Lock information for stack operations\n */\nexport interface LockInfo {\n /** Lock owner (e.g., username, CI job ID) */\n owner: string;\n\n /** Lock acquisition timestamp (Unix milliseconds) */\n timestamp: number;\n\n /** Lock expiration timestamp (Unix milliseconds) */\n expiresAt: number;\n\n /** Optional operation being performed */\n operation?: string;\n}\n\n/**\n * Change type for resource diff\n */\nexport type ChangeType = 'CREATE' | 'UPDATE' | 'DELETE' | 'NO_CHANGE';\n\n/**\n * Resource change information\n */\nexport interface ResourceChange {\n /** Logical ID from CloudFormation template */\n logicalId: string;\n\n /** Type of change */\n changeType: ChangeType;\n\n /** Resource type */\n resourceType: string;\n\n /** Current properties (for UPDATE/DELETE) */\n currentProperties?: Record<string, unknown>;\n\n /** Desired properties (for CREATE/UPDATE) */\n desiredProperties?: Record<string, unknown>;\n\n /** Property-level changes (for UPDATE) */\n propertyChanges?: PropertyChange[];\n\n /**\n * `DeletionPolicy` / `UpdateReplacePolicy` attribute changes (schema v5+).\n * Populated when the template attribute differs from the value recorded in\n * cdkd state. AWS has no API to mutate these attributes per-resource, so\n * the deploy engine handles the change by updating cdkd state only — no\n * provider call. UPDATE classification still fires when only these change\n * (DiffCalculator does not stay at `NO_CHANGE`), so users see the diff\n * instead of `No changes detected`.\n */\n attributeChanges?: AttributeChange[];\n}\n\n/**\n * Template-level resource attribute change (schema v5+).\n *\n * `DeletionPolicy` / `UpdateReplacePolicy` are CloudFormation template\n * metadata — they have no AWS API per-resource and are mutated through the\n * cdkd state record alone.\n */\nexport interface AttributeChange {\n /** Attribute name: `DeletionPolicy` or `UpdateReplacePolicy`. */\n attribute: 'DeletionPolicy' | 'UpdateReplacePolicy';\n oldValue: string | undefined;\n newValue: string | undefined;\n}\n\n/**\n * Returns true when a recorded `DeletionPolicy` should prevent cdkd from\n * deleting the underlying AWS resource. `Retain` and `RetainExceptOnCreate`\n * both keep the resource around; `Delete` / `Snapshot` / undefined all\n * fall through to the normal delete path. Shared between\n * `runDestroyForStack` (state-only, no template) and `DeployEngine`'s\n * DELETE branch (state-preferred, template-fallback) so the two paths\n * cannot drift on the policy semantics. Lives here (not in\n * deploy-engine or destroy-runner) because both consumers already\n * depend on this module — placing it in either would create a cycle.\n */\nexport function shouldRetainResource(\n deletionPolicy: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined\n): boolean {\n return deletionPolicy === 'Retain' || deletionPolicy === 'RetainExceptOnCreate';\n}\n\n/**\n * Property-level change\n */\nexport interface PropertyChange {\n /** Property path (e.g., \"Code.S3Key\") */\n path: string;\n\n /** Old value */\n oldValue: unknown;\n\n /** New value */\n newValue: unknown;\n\n /** Whether this change requires replacement */\n requiresReplacement: boolean;\n}\n","import {\n S3Client,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n NoSuchKey,\n} from '@aws-sdk/client-s3';\nimport {\n STATE_SCHEMA_VERSION_CURRENT,\n STATE_SCHEMA_VERSIONS_READABLE,\n type StackState,\n} from '../types/state.js';\nimport type { StateBackendConfig } from '../types/config.js';\nimport { getLogger } from '../utils/logger.js';\nimport { StateError, normalizeAwsError } from '../utils/error-handler.js';\nimport { resolveBucketRegion } from '../utils/aws-region-resolver.js';\n\n/**\n * Identifier of a state record. The legacy layout (`version: 1`) didn't have\n * region in the S3 key, so reads from the legacy key carry `region:\n * undefined`.\n */\nexport interface StackStateRef {\n stackName: string;\n /** Region of the state. `undefined` ONLY for legacy `version: 1` records. */\n region?: string;\n}\n\n/**\n * The `version: 1` legacy state key under the `cdkd/` prefix. Two layers\n * deep — split off into a constant so call sites can clearly distinguish\n * \"two-segment legacy key\" from \"three-segment new key\".\n */\nconst LEGACY_KEY_DEPTH = 2;\n/** The `version: 2` region-prefixed key. */\nconst NEW_KEY_DEPTH = 3;\n\n/**\n * Options used to reconstruct the S3Client if the bucket lives in a region\n * different from the one the initial client was built for.\n *\n * Mirrors {@link AwsClientConfig} from `aws-clients.ts` but kept local so\n * the state backend doesn't depend on the CLI-side AwsClients wrapper.\n */\nexport interface S3ClientOptions {\n region?: string;\n profile?: string;\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n };\n}\n\n/**\n * S3-based state backend using conditional writes for optimistic locking.\n *\n * State keys are region-scoped (`{prefix}/{stackName}/{region}/state.json`)\n * to prevent two regions of the same stackName from overwriting each other's\n * state. Legacy `{prefix}/{stackName}/state.json` keys (schema `version: 1`)\n * are still readable; the next `saveState` for that stack auto-migrates by\n * writing the new key and deleting the legacy one.\n *\n * The state bucket can live in a different AWS region from the rest of the\n * cdkd CLI's resource provisioning. Before the first state operation, this\n * backend resolves the bucket's actual region via `GetBucketLocation` and,\n * if it differs from the client's configured region, rebuilds the S3Client\n * for that region. Provisioning clients are unaffected — only the\n * state-bucket S3 client is region-corrected.\n */\nexport class S3StateBackend {\n private logger = getLogger().child('S3StateBackend');\n private s3Client: S3Client;\n private config: StateBackendConfig;\n private clientOpts: S3ClientOptions;\n private clientResolved = false;\n private resolveInFlight: Promise<void> | null = null;\n\n constructor(s3Client: S3Client, config: StateBackendConfig, clientOpts: S3ClientOptions = {}) {\n this.s3Client = s3Client;\n this.config = config;\n this.clientOpts = clientOpts;\n }\n\n /**\n * Get the new (region-scoped) S3 key for a stack's state file.\n */\n private getStateKey(stackName: string, region: string): string {\n return `${this.config.prefix}/${stackName}/${region}/state.json`;\n }\n\n /**\n * Get the legacy (pre-region-prefix) S3 key for a stack's state file.\n * Used for backwards-compatible reads and for the migration delete.\n */\n private getLegacyStateKey(stackName: string): string {\n return `${this.config.prefix}/${stackName}/state.json`;\n }\n\n /**\n * Resolve the state bucket's actual region and, if it differs from the\n * client's currently-configured region, replace the S3Client with one\n * pointed at the bucket's region.\n *\n * This is idempotent: subsequent calls return immediately. Concurrent\n * callers (e.g. when several public methods race during a parallel deploy)\n * share a single in-flight resolution promise so we never issue more than\n * one `GetBucketLocation` per backend.\n *\n * Errors from `GetBucketLocation` are deliberately swallowed by\n * `resolveBucketRegion` — the resolver returns `fallbackRegion` so the\n * caller can surface the more actionable downstream error (e.g. the\n * `HeadBucket` 404 routed via `normalizeAwsError`).\n */\n private async ensureClientForBucket(): Promise<void> {\n if (this.clientResolved) return;\n if (this.resolveInFlight) return this.resolveInFlight;\n\n this.resolveInFlight = (async (): Promise<void> => {\n try {\n const currentRegion = await this.s3Client.config.region();\n const fallbackRegion = typeof currentRegion === 'string' ? currentRegion : undefined;\n const bucketRegion = await resolveBucketRegion(this.config.bucket, {\n ...(this.clientOpts.profile && { profile: this.clientOpts.profile }),\n ...(this.clientOpts.credentials && { credentials: this.clientOpts.credentials }),\n ...(fallbackRegion && { fallbackRegion }),\n });\n\n if (bucketRegion !== currentRegion) {\n this.logger.debug(\n `State bucket '${this.config.bucket}' is in '${bucketRegion}' (client was '${currentRegion}'); rebuilding S3 client.`\n );\n const oldClient = this.s3Client;\n this.s3Client = new S3Client({\n region: bucketRegion,\n ...(this.clientOpts.profile && { profile: this.clientOpts.profile }),\n ...(this.clientOpts.credentials && { credentials: this.clientOpts.credentials }),\n // Suppress \"Are you using a Stream of unknown length\" warning,\n // matching the suppression in AwsClients.\n logger: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },\n });\n oldClient.destroy();\n }\n this.clientResolved = true;\n } finally {\n this.resolveInFlight = null;\n }\n })();\n\n return this.resolveInFlight;\n }\n\n /**\n * Verify that the configured state bucket exists.\n *\n * Called early in deploy/destroy to fail fast before expensive work\n * (asset publishing, Docker builds) runs against a missing bucket.\n *\n * Errors are routed through {@link normalizeAwsError} so the AWS SDK v3\n * synthetic `UnknownError` (e.g. cross-region HEAD) becomes a concrete\n * \"Bucket does not exist\" / \"Access denied\" / \"different region\" message.\n */\n async verifyBucketExists(): Promise<void> {\n await this.ensureClientForBucket();\n try {\n await this.s3Client.send(new HeadBucketCommand({ Bucket: this.config.bucket }));\n } catch (error) {\n const name = (error as { name?: string }).name;\n if (name === 'NotFound' || name === 'NoSuchBucket') {\n throw new StateError(\n `State bucket '${this.config.bucket}' does not exist. ` +\n `Run 'cdkd bootstrap' to create it, or specify an existing bucket via ` +\n `--state-bucket, CDKD_STATE_BUCKET, or cdk.json context.cdkd.stateBucket.`\n );\n }\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'HeadBucket',\n });\n throw new StateError(\n `Failed to verify state bucket '${this.config.bucket}': ${normalized.message}`,\n normalized\n );\n }\n }\n\n /**\n * Check if state exists for a stack in the given region.\n *\n * Returns true for either layout: the new region-scoped key, or the legacy\n * key when its embedded `region` matches the requested region. This lets\n * `cdkd state orphan <stack> --region X` and `cdkd destroy <stack>` see legacy\n * state without forcing a write-through migration first.\n */\n async stateExists(stackName: string, region: string): Promise<boolean> {\n await this.ensureClientForBucket();\n const newKey = this.getStateKey(stackName, region);\n\n if (await this.headObject(newKey)) {\n return true;\n }\n\n return this.legacyMatchesRegion(stackName, region);\n }\n\n /**\n * Get state for a stack, transparently falling back to the legacy key.\n *\n * Lookup order:\n * 1. `{prefix}/{stackName}/{region}/state.json` (current `version: 2` key).\n * 2. `{prefix}/{stackName}/state.json` (legacy `version: 1` key) — only\n * accepted if its embedded `region` matches the requested region.\n *\n * When a legacy hit is returned, `migrationPending` is `true`. Callers that\n * subsequently `saveState` automatically migrate by writing the new key and\n * deleting the legacy one (see `saveState`'s `legacyMigration` argument).\n *\n * Note: S3 returns ETag with surrounding quotes (e.g., `\"abc123\"`). We\n * preserve the quotes — they are required for `IfMatch` conditions.\n */\n async getState(\n stackName: string,\n region: string\n ): Promise<{ state: StackState; etag: string; migrationPending?: boolean } | null> {\n await this.ensureClientForBucket();\n const newKey = this.getStateKey(stackName, region);\n\n // 1. Try new region-scoped key first.\n try {\n this.logger.debug(`Getting state for stack: ${stackName} (${region})`);\n\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: newKey,\n })\n );\n\n if (!response.Body) {\n throw new StateError(`State file for stack '${stackName}' (${region}) has no body`);\n }\n if (!response.ETag) {\n throw new StateError(`State file for stack '${stackName}' (${region}) has no ETag`);\n }\n\n const bodyString = await response.Body.transformToString();\n const state = this.parseStateBody(bodyString, stackName);\n this.logger.debug(`Retrieved state: ${stackName} (${region}), ETag: ${response.ETag}`);\n return { state, etag: response.ETag };\n } catch (error) {\n if (!isNoSuchKey(error)) {\n if (error instanceof StateError) throw error;\n throw new StateError(\n `Failed to get state for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n this.logger.debug(`No state at new key for stack: ${stackName} (${region})`);\n }\n\n // 2. Fall back to legacy key when it exists AND its region matches.\n const legacy = await this.tryGetLegacy(stackName, region);\n if (legacy) {\n this.logger.warn(\n `Loaded legacy state for stack '${stackName}' from '${this.getLegacyStateKey(stackName)}'. ` +\n `It will be migrated to the region-scoped layout on next save.`\n );\n return { ...legacy, migrationPending: true };\n }\n\n return null;\n }\n\n /**\n * Save state for a stack with optimistic locking.\n *\n * Always writes to the new region-scoped key. The state body is rewritten\n * with `version: 2` and the supplied region.\n *\n * If the caller observed `migrationPending: true` from `getState`, it\n * should pass the legacy ETag back via `expectedEtag` AND set\n * `migrateLegacy: true`. After the new key is written successfully, the\n * legacy key is deleted to complete migration. The legacy delete is a\n * best-effort follow-up — a failure is logged but does not unwind the new\n * write.\n *\n * @param stackName Stack name\n * @param region Target region (load-bearing — part of the S3 key)\n * @param state State to save\n * @param options Optimistic-lock ETag + legacy-migration flag\n * @returns New ETag (with quotes, e.g., `\"abc123\"`)\n */\n async saveState(\n stackName: string,\n region: string,\n state: StackState,\n options: { expectedEtag?: string; migrateLegacy?: boolean } = {}\n ): Promise<string> {\n await this.ensureClientForBucket();\n const newKey = this.getStateKey(stackName, region);\n const { expectedEtag, migrateLegacy } = options;\n\n // Normalize the body: schema version + region are load-bearing on disk.\n const body: StackState = {\n ...state,\n version: STATE_SCHEMA_VERSION_CURRENT,\n stackName,\n region,\n };\n\n try {\n this.logger.debug(\n `Saving state: ${stackName} (${region})${expectedEtag ? `, expected ETag: ${expectedEtag}` : ''}`\n );\n\n const bodyString = JSON.stringify(body, null, 2);\n const response = await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.config.bucket,\n Key: newKey,\n Body: bodyString,\n ContentLength: Buffer.byteLength(bodyString),\n ContentType: 'application/json',\n // The legacy ETag is for a different key; only forward it when we're\n // updating in-place at the new key.\n ...(!migrateLegacy && expectedEtag && { IfMatch: expectedEtag }),\n })\n );\n\n if (!response.ETag) {\n throw new StateError(\n `No ETag returned after saving state for stack '${stackName}' (${region})`\n );\n }\n this.logger.debug(`State saved: ${stackName} (${region}), new ETag: ${response.ETag}`);\n\n // Migration tail: best-effort delete of the legacy key. We don't fail\n // the save if this errors — the new key is the source of truth and a\n // residual legacy key is recoverable (next call will migrate again).\n if (migrateLegacy) {\n try {\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n this.logger.info(\n `Migrated state for stack '${stackName}' to region-scoped layout (${region})`\n );\n } catch (deleteError) {\n this.logger.warn(\n `Migrated stack '${stackName}' to new key, but failed to delete legacy key: ` +\n `${deleteError instanceof Error ? deleteError.message : String(deleteError)}`\n );\n }\n }\n\n return response.ETag;\n } catch (error) {\n if ((error as { name: string }).name === 'PreconditionFailed') {\n throw new StateError(\n `State has been modified by another process. Expected ETag: ${expectedEtag}, but state has changed.`\n );\n }\n\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'PutObject',\n });\n throw new StateError(\n `Failed to save state for stack '${stackName}' (${region}): ${normalized.message}`,\n normalized\n );\n }\n }\n\n /**\n * Delete state for a stack in the given region.\n *\n * Removes both the new key and the legacy key (if present). Legacy removal\n * is region-conditional: a legacy state file with a different `region`\n * field is left alone.\n */\n async deleteState(stackName: string, region: string): Promise<void> {\n await this.ensureClientForBucket();\n try {\n this.logger.debug(`Deleting state: ${stackName} (${region})`);\n\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getStateKey(stackName, region),\n })\n );\n\n // Sweep the legacy key only if it belongs to the same region.\n if (await this.legacyMatchesRegion(stackName, region)) {\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n this.logger.debug(`Deleted legacy state for stack: ${stackName}`);\n }\n\n this.logger.debug(`State deleted: ${stackName} (${region})`);\n } catch (error) {\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'DeleteObject',\n });\n throw new StateError(\n `Failed to delete state for stack '${stackName}' (${region}): ${normalized.message}`,\n normalized\n );\n }\n }\n\n /**\n * List all stacks with state in the bucket.\n *\n * Returns one `{stackName, region}` pair per state file. Both layouts\n * are enumerated:\n *\n * - `{prefix}/{stackName}/{region}/state.json` (new) — `region` is the\n * path segment.\n * - `{prefix}/{stackName}/state.json` (legacy) — `region` is read from the\n * state body when present, otherwise `undefined`.\n *\n * Pairs are deduplicated by `(stackName, region)` so a stack mid-migration\n * shows up exactly once.\n */\n async listStacks(): Promise<StackStateRef[]> {\n await this.ensureClientForBucket();\n try {\n this.logger.debug('Listing all stacks');\n\n const prefix = `${this.config.prefix}/`;\n const refs: StackStateRef[] = [];\n const seen = new Set<string>();\n let continuationToken: string | undefined;\n\n do {\n const response = await this.s3Client.send(\n new ListObjectsV2Command({\n Bucket: this.config.bucket,\n Prefix: prefix,\n ...(continuationToken && { ContinuationToken: continuationToken }),\n })\n );\n\n for (const obj of response.Contents ?? []) {\n const key = obj.Key;\n if (!key) continue;\n if (!key.endsWith('/state.json')) continue;\n\n const rest = key.slice(prefix.length);\n const segments = rest.split('/');\n\n // New key: {stackName}/{region}/state.json\n if (segments.length === NEW_KEY_DEPTH) {\n const [stackName, region] = segments;\n if (!stackName || !region) continue;\n const dedupeKey = `${stackName}\\0${region}`;\n if (!seen.has(dedupeKey)) {\n seen.add(dedupeKey);\n refs.push({ stackName, region });\n }\n continue;\n }\n\n // Legacy key: {stackName}/state.json\n if (segments.length === LEGACY_KEY_DEPTH) {\n const [stackName] = segments;\n if (!stackName) continue;\n const region = await this.readLegacyRegion(stackName);\n const dedupeKey = `${stackName}\\0${region ?? ''}`;\n if (!seen.has(dedupeKey)) {\n seen.add(dedupeKey);\n refs.push({ stackName, ...(region ? { region } : {}) });\n }\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken);\n\n this.logger.debug(`Found ${refs.length} stack(s) across regions`);\n return refs;\n } catch (error) {\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'ListObjectsV2',\n });\n throw new StateError(`Failed to list stacks: ${normalized.message}`, normalized);\n }\n }\n\n /**\n * HeadObject probe — returns true on 200, false on NotFound. Other errors\n * propagate so we don't accidentally swallow IAM denials.\n */\n private async headObject(key: string): Promise<boolean> {\n try {\n await this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n return true;\n } catch (error) {\n if (isNoSuchKey(error) || (error as { name?: string }).name === 'NotFound') {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Read the legacy state's `region` field. Used for region matching during\n * `stateExists` / `deleteState` and for assigning a region to legacy\n * entries during `listStacks`.\n */\n private async readLegacyRegion(stackName: string): Promise<string | undefined> {\n try {\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n if (!response.Body) return undefined;\n const bodyString = await response.Body.transformToString();\n const state = JSON.parse(bodyString) as Partial<StackState>;\n return typeof state.region === 'string' ? state.region : undefined;\n } catch (error) {\n if (isNoSuchKey(error)) return undefined;\n // Don't fail the whole list on a single bad legacy file — log & skip.\n this.logger.debug(\n `Could not read legacy state region for '${stackName}': ${error instanceof Error ? error.message : String(error)}`\n );\n return undefined;\n }\n }\n\n private async legacyMatchesRegion(stackName: string, region: string): Promise<boolean> {\n const legacyRegion = await this.readLegacyRegion(stackName);\n return legacyRegion === region;\n }\n\n /**\n * Try to read the legacy `version: 1` state. Returns null when the legacy\n * key is missing or its embedded region does not match the caller's region.\n */\n private async tryGetLegacy(\n stackName: string,\n region: string\n ): Promise<{ state: StackState; etag: string } | null> {\n try {\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n\n if (!response.Body || !response.ETag) {\n return null;\n }\n\n const bodyString = await response.Body.transformToString();\n const state = this.parseStateBody(bodyString, stackName);\n\n // Region gate: the same `stackName` may have lived in a different region\n // before the user changed `env.region`. We do NOT want to silently load\n // that record for a different target region — that's the silent-failure\n // bug PR 1 fixes.\n if (state.region && state.region !== region) {\n this.logger.debug(\n `Legacy state for stack '${stackName}' has region '${state.region}', ` +\n `not '${region}' — skipping legacy fallback.`\n );\n return null;\n }\n\n return { state, etag: response.ETag };\n } catch (error) {\n if (isNoSuchKey(error)) return null;\n throw new StateError(\n `Failed to get legacy state for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Parse a state body and validate the schema version. Future-proofs against\n * a binary that predates schema version `N` reading a `version: N+1` blob:\n * the old binary would otherwise treat unknown fields as defaults and\n * silently lose data on the next save.\n */\n private parseStateBody(bodyString: string, stackName: string): StackState {\n let parsed: StackState;\n try {\n parsed = JSON.parse(bodyString) as StackState;\n } catch (error) {\n throw new StateError(\n `State file for stack '${stackName}' is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n const v = parsed.version;\n if (v !== undefined && !STATE_SCHEMA_VERSIONS_READABLE.includes(v)) {\n throw new StateError(\n `Unsupported state schema version ${String(v)} for stack '${stackName}'. ` +\n `This cdkd binary supports versions ${STATE_SCHEMA_VERSIONS_READABLE.join(', ')}. ` +\n `Upgrade cdkd to a version that supports schema ${String(v)}.`\n );\n }\n\n return parsed;\n }\n}\n\n/**\n * Treat S3 NoSuchKey-equivalents uniformly. The SDK throws `NoSuchKey` from\n * `GetObject` and `{name: 'NoSuchKey'}` from low-level callsites; HeadObject\n * raises `{name: 'NotFound'}` instead.\n */\nfunction isNoSuchKey(error: unknown): boolean {\n if (error instanceof NoSuchKey) return true;\n const name = (error as { name?: string } | null)?.name;\n return name === 'NoSuchKey';\n}\n","import {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n NoSuchKey,\n S3ServiceException,\n} from '@aws-sdk/client-s3';\nimport type { LockInfo } from '../types/state.js';\nimport type { StateBackendConfig } from '../types/config.js';\nimport { getLogger } from '../utils/logger.js';\nimport { LockError } from '../utils/error-handler.js';\nimport { hostname } from 'os';\n\n/**\n * Options for LockManager constructor\n */\nexport interface LockManagerOptions {\n /** Lock TTL in minutes (default: 30) */\n ttlMinutes?: number;\n}\n\n/**\n * S3-based lock manager using conditional writes (If-None-Match)\n *\n * Implements distributed locking using S3's If-None-Match: \"*\" condition\n * which ensures atomic lock acquisition.\n *\n * Locks have a TTL (time-to-live). Expired locks are automatically cleaned up\n * during acquisition attempts.\n */\nexport class LockManager {\n private logger = getLogger().child('LockManager');\n private s3Client: S3Client;\n private config: StateBackendConfig;\n private readonly ttlMs: number;\n\n constructor(s3Client: S3Client, config: StateBackendConfig, options?: LockManagerOptions) {\n this.s3Client = s3Client;\n this.config = config;\n const ttlMinutes = options?.ttlMinutes ?? 30;\n this.ttlMs = ttlMinutes * 60 * 1000;\n }\n\n /**\n * Get the S3 key for a stack's lock file.\n *\n * Locks are region-scoped, mirroring the state key layout\n * (`{prefix}/{stackName}/{region}/lock.json`). Two regions of the same\n * stackName can therefore be operated on in parallel without contention,\n * matching cdkd's parallel execution model.\n *\n * The `region` argument is required for new callers; for backwards\n * compatibility with `state list --long` (which only sees stack names),\n * passing `undefined` falls back to the legacy `{prefix}/{stackName}/lock.json`\n * key — that mode is purely for legacy lock cleanup and is NOT used by\n * deploy / destroy / diff anymore.\n */\n private getLockKey(stackName: string, region: string | undefined): string {\n if (region === undefined) {\n return `${this.config.prefix}/${stackName}/lock.json`;\n }\n return `${this.config.prefix}/${stackName}/${region}/lock.json`;\n }\n\n /**\n * Get default lock owner identifier\n */\n private getDefaultOwner(): string {\n try {\n const host = hostname();\n const user = process.env['USER'] || process.env['USERNAME'] || 'unknown';\n const pid = process.pid;\n return `${user}@${host}:${pid}`;\n } catch {\n return `cdkd:${process.pid}`;\n }\n }\n\n /**\n * Check if a lock is expired based on its expiresAt field\n */\n private isLockExpired(lockInfo: LockInfo): boolean {\n return Date.now() >= lockInfo.expiresAt;\n }\n\n /**\n * Format a human-readable duration from milliseconds\n */\n private formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes}m${remainingSeconds}s`;\n }\n\n /**\n * Try to acquire a lock for a stack\n *\n * Uses If-None-Match: \"*\" to ensure atomic lock acquisition.\n * If an expired lock exists, it will be cleaned up and re-acquired.\n *\n * @param stackName Stack name\n * @param region Target region (lock key is region-scoped)\n * @param owner Lock owner identifier (defaults to user@hostname:pid)\n * @param operation Operation being performed (e.g., \"deploy\", \"destroy\")\n */\n async acquireLock(\n stackName: string,\n region: string,\n owner?: string,\n operation?: string\n ): Promise<boolean> {\n const key = this.getLockKey(stackName, region);\n const lockOwner = owner || this.getDefaultOwner();\n const now = Date.now();\n\n const lockInfo: LockInfo = {\n owner: lockOwner,\n timestamp: now,\n expiresAt: now + this.ttlMs,\n ...(operation && { operation }),\n };\n\n try {\n this.logger.debug(`Attempting to acquire lock for stack: ${stackName} (${region})`);\n\n const lockBody = JSON.stringify(lockInfo, null, 2);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n Body: lockBody,\n ContentLength: Buffer.byteLength(lockBody),\n ContentType: 'application/json',\n IfNoneMatch: '*', // Only succeed if object doesn't exist\n })\n );\n\n this.logger.debug(`Lock acquired for stack: ${stackName} (${region}), owner: ${lockOwner}`);\n return true;\n } catch (error) {\n // Check for PreconditionFailed error (S3 condition not met - lock already exists)\n if (error instanceof S3ServiceException && error.name === 'PreconditionFailed') {\n this.logger.debug(`Lock already exists for stack: ${stackName} (${region})`);\n\n // Check if the existing lock is expired\n const existingLock = await this.getLockInfo(stackName, region);\n if (existingLock && this.isLockExpired(existingLock)) {\n this.logger.info(\n `Expired lock detected for stack: ${stackName} (${region}, owner: ${existingLock.owner}, ` +\n `expired ${this.formatDuration(now - existingLock.expiresAt)} ago). Cleaning up...`\n );\n\n // Delete the expired lock and retry acquisition\n await this.deleteLock(stackName, region);\n\n // Retry once after cleaning up expired lock\n try {\n const retryBody = JSON.stringify(lockInfo, null, 2);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n Body: retryBody,\n ContentLength: Buffer.byteLength(retryBody),\n ContentType: 'application/json',\n IfNoneMatch: '*',\n })\n );\n\n this.logger.debug(\n `Lock acquired for stack: ${stackName} (${region}) after expired lock cleanup, owner: ${lockOwner}`\n );\n return true;\n } catch (retryError) {\n if (\n retryError instanceof S3ServiceException &&\n retryError.name === 'PreconditionFailed'\n ) {\n // Another process acquired the lock between our delete and retry\n this.logger.debug(\n `Lock was acquired by another process during expired lock cleanup for stack: ${stackName} (${region})`\n );\n return false;\n }\n throw retryError;\n }\n }\n\n return false;\n }\n\n throw new LockError(\n `Failed to acquire lock for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get current lock information.\n *\n * `region` is required for the new region-scoped lock layout. Pass\n * `undefined` only to inspect a legacy `{prefix}/{stackName}/lock.json`\n * file (e.g. for state-listing tools that don't yet know the region).\n */\n async getLockInfo(stackName: string, region: string | undefined): Promise<LockInfo | null> {\n const key = this.getLockKey(stackName, region);\n\n try {\n this.logger.debug(`Getting lock info for stack: ${stackName}`);\n\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n\n if (!response.Body) {\n throw new LockError(`Lock file for stack '${stackName}' has no body`);\n }\n\n const bodyString = await response.Body.transformToString();\n const lockInfo = JSON.parse(bodyString) as LockInfo;\n\n this.logger.debug(`Lock info for stack: ${stackName}:`, lockInfo);\n\n return lockInfo;\n } catch (error) {\n if (error instanceof NoSuchKey) {\n this.logger.debug(`No lock exists for stack: ${stackName}`);\n return null;\n }\n\n if (error instanceof LockError) {\n throw error;\n }\n\n throw new LockError(\n `Failed to get lock info for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Check whether a lock currently exists for a stack\n *\n * Returns true if a lock file is present in S3 (regardless of expiry).\n * This is intended for read-only inspection (e.g. `cdkd state list --long`),\n * not for acquisition decisions — use `acquireLock` for that, which has its\n * own expired-lock cleanup logic.\n */\n async isLocked(stackName: string, region: string | undefined): Promise<boolean> {\n const lockInfo = await this.getLockInfo(stackName, region);\n return lockInfo !== null;\n }\n\n /**\n * Release a lock for a stack\n */\n async releaseLock(stackName: string, region: string): Promise<void> {\n const key = this.getLockKey(stackName, region);\n\n try {\n this.logger.debug(`Releasing lock for stack: ${stackName} (${region})`);\n\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n\n this.logger.debug(`Lock released for stack: ${stackName} (${region})`);\n } catch (error) {\n throw new LockError(\n `Failed to release lock for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Force release a lock regardless of owner or expiry status\n *\n * This is intended for CLI usage (e.g., --force-unlock flag) when a lock\n * is stuck and needs manual intervention.\n *\n * Pass `region: undefined` to operate on a legacy\n * `{prefix}/{stackName}/lock.json` file.\n */\n async forceReleaseLock(stackName: string, region: string | undefined): Promise<void> {\n const lockInfo = await this.getLockInfo(stackName, region);\n\n if (!lockInfo) {\n this.logger.warn(\n `No lock to force release for stack: ${stackName}${region ? ` (${region})` : ''}`\n );\n return;\n }\n\n this.logger.warn(\n `Force releasing lock for stack: ${stackName}${region ? ` (${region})` : ''}, ` +\n `owner: ${lockInfo.owner}` +\n `${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ''}` +\n `, expired: ${this.isLockExpired(lockInfo)}`\n );\n\n await this.deleteLock(stackName, region);\n }\n\n /**\n * Internal method to delete the lock file from S3\n */\n private async deleteLock(stackName: string, region: string | undefined): Promise<void> {\n const key = this.getLockKey(stackName, region);\n\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n }\n\n /**\n * Acquire lock with retry logic\n *\n * Retries up to maxRetries times with retryDelay between attempts.\n * If lock is expired, cleans it up automatically.\n * On failure, provides helpful message with lock owner and expiry information.\n *\n * @param stackName Stack name\n * @param owner Lock owner identifier\n * @param operation Operation being performed\n * @param maxRetries Maximum number of retries (default: 3)\n * @param retryDelay Delay between retries in milliseconds (default: 2000)\n */\n async acquireLockWithRetry(\n stackName: string,\n region: string,\n owner?: string,\n operation?: string,\n maxRetries = 3,\n retryDelay = 2000\n ): Promise<void> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const acquired = await this.acquireLock(stackName, region, owner, operation);\n\n if (acquired) {\n return;\n }\n\n // Lock exists and is not expired - show info and possibly retry\n const lockInfo = await this.getLockInfo(stackName, region);\n\n if (lockInfo) {\n const remainingMs = lockInfo.expiresAt - Date.now();\n\n if (attempt < maxRetries) {\n this.logger.info(\n `Stack '${stackName}' (${region}) is locked by ${lockInfo.owner}` +\n `${lockInfo.operation ? ` (operation: ${lockInfo.operation})` : ''}` +\n `. Lock expires in ${this.formatDuration(remainingMs)}.` +\n ` Retrying in ${this.formatDuration(retryDelay)}... (attempt ${attempt + 1}/${maxRetries})`\n );\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n continue;\n }\n }\n }\n\n // Failed to acquire lock after all retries\n const lockInfo = await this.getLockInfo(stackName, region);\n const expiresIn = lockInfo ? this.formatDuration(lockInfo.expiresAt - Date.now()) : 'unknown';\n\n throw new LockError(\n `Failed to acquire lock for stack '${stackName}' (${region}) after ${maxRetries + 1} attempts. ` +\n (lockInfo\n ? `Locked by: ${lockInfo.owner}` +\n `${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ''}` +\n `, expires in: ${expiresIn}. ` +\n `Use --force-unlock to manually release the lock.`\n : 'Lock exists but could not read lock info.')\n );\n }\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * CloudFormation template parser\n *\n * Provides utilities for parsing and extracting information from\n * CloudFormation templates\n */\nexport class TemplateParser {\n private logger = getLogger().child('TemplateParser');\n\n /**\n * Extract all resource logical IDs from template\n */\n getResourceIds(template: CloudFormationTemplate): string[] {\n return Object.keys(template.Resources);\n }\n\n /**\n * Get a specific resource from template\n */\n getResource(template: CloudFormationTemplate, logicalId: string): TemplateResource | undefined {\n return template.Resources[logicalId];\n }\n\n /**\n * Extract all dependencies for a resource\n *\n * Analyzes:\n * - DependsOn attribute\n * - Ref intrinsic functions\n * - Fn::GetAtt intrinsic functions\n */\n extractDependencies(resource: TemplateResource): Set<string> {\n const dependencies = new Set<string>();\n\n // 1. DependsOn attribute\n if (resource.DependsOn) {\n const dependsOn = Array.isArray(resource.DependsOn)\n ? resource.DependsOn\n : [resource.DependsOn];\n\n dependsOn.forEach((dep) => {\n if (typeof dep === 'string') {\n dependencies.add(dep);\n }\n });\n }\n\n // 2. Ref and Fn::GetAtt in Properties\n if (resource.Properties) {\n this.extractRefsFromValue(resource.Properties, dependencies);\n }\n\n // 3. Ref and Fn::GetAtt in other attributes (Metadata, UpdatePolicy, etc.)\n if (resource.Metadata) {\n this.extractRefsFromValue(resource.Metadata, dependencies);\n }\n\n return dependencies;\n }\n\n /**\n * Recursively extract Ref and Fn::GetAtt from a value\n */\n private extractRefsFromValue(value: unknown, dependencies: Set<string>): void {\n if (value === null || value === undefined) {\n return;\n }\n\n // Check if value is an object\n if (typeof value !== 'object') {\n return;\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n value.forEach((item) => this.extractRefsFromValue(item, dependencies));\n return;\n }\n\n // Handle objects\n const obj = value as Record<string, unknown>;\n\n // Check for Ref\n if ('Ref' in obj && typeof obj['Ref'] === 'string') {\n // Ignore pseudo parameters (AWS::Region, AWS::AccountId, etc.)\n if (!obj['Ref'].startsWith('AWS::')) {\n dependencies.add(obj['Ref']);\n }\n return;\n }\n\n // Check for Fn::GetAtt\n if ('Fn::GetAtt' in obj) {\n const getAtt = obj['Fn::GetAtt'];\n if (Array.isArray(getAtt) && getAtt.length >= 1 && typeof getAtt[0] === 'string') {\n dependencies.add(getAtt[0]);\n }\n return;\n }\n\n // Check for Fn::Sub\n // 1-arg form: \"Fn::Sub\": \"string with ${X} or ${X.Attr}\"\n // 2-arg form: \"Fn::Sub\": [\"string with ${X}\", { X: <value> }]\n // Per the CloudFormation spec, when ${X} appears in the body and X is NOT\n // in the explicit variable map (2-arg form), X resolves to Ref X — which\n // can point at a same-stack resource. The DAG must treat that as a real\n // dependency edge so the referenced resource is created first; otherwise\n // the resolver races and falls back to the literal placeholder, which AWS\n // rejects (see #275).\n if ('Fn::Sub' in obj) {\n const subValue = obj['Fn::Sub'];\n let body: string | undefined;\n let mapKeys: Set<string> | undefined;\n if (typeof subValue === 'string') {\n body = subValue;\n } else if (\n Array.isArray(subValue) &&\n subValue.length >= 1 &&\n typeof subValue[0] === 'string'\n ) {\n body = subValue[0];\n const variables: unknown = subValue[1];\n if (variables && typeof variables === 'object' && !Array.isArray(variables)) {\n const varMap = variables as Record<string, unknown>;\n mapKeys = new Set(Object.keys(varMap));\n // Recurse into the variable-map values — they may contain Ref / GetAtt\n // intrinsics that produce their own dependencies.\n Object.values(varMap).forEach((v) => this.extractRefsFromValue(v, dependencies));\n }\n }\n if (body !== undefined) {\n for (const match of body.matchAll(/\\$\\{([^}]+)\\}/g)) {\n const placeholder = match[1];\n if (!placeholder) continue;\n // ${X.AttrName} is an implicit Fn::GetAtt — depend on X (the prefix).\n // ${X} is an implicit Ref to X.\n const dot = placeholder.indexOf('.');\n const name = dot >= 0 ? placeholder.slice(0, dot) : placeholder;\n if (!name) continue;\n // Skip pseudo parameters (AWS::Region, AWS::AccountId, etc.).\n if (name.startsWith('AWS::')) continue;\n // Skip names provided by the 2-arg variable map.\n if (mapKeys?.has(name)) continue;\n dependencies.add(name);\n }\n }\n return;\n }\n\n // Check for Fn::Join / Fn::Select / Fn::Split\n // Fn::Join: [<delimiter: string>, [<item1>, <item2>, ...]]\n // Fn::Select: [<index: number-or-Ref>, <array-or-intrinsic>]\n // Fn::Split: [<delimiter: string>, <source-string-or-intrinsic>]\n // CDK emits these (especially Fn::Join) to construct ARNs that embed\n // sibling-resource Refs / Fn::GetAtt inside the array arguments — e.g.\n // DynamoDB `Table.tableArn` synthesizes as\n // Fn::Join: [':', ['arn', 'aws', 'dynamodb', {Ref:'AWS::Region'},\n // {Ref:'AWS::AccountId'}, {Fn::Join:['/',['table',{Ref:'MyTable'}]]}]]\n // and Fn::Select+Fn::Split is the canonical \"extract substring from ARN\"\n // pattern (Fn::Select: [5, Fn::Split: [':', Fn::GetAtt: [..., 'Arn']]]).\n // The buried Ref / Fn::GetAtt MUST contribute a DAG edge so the consumer\n // is ordered after its dependency; otherwise the deploy races. Explicit\n // recursion through each intrinsic's array argument keeps that support\n // load-bearing instead of relying on the generic fall-through below to\n // accidentally find them (see issue #286 — same class as #275/#276).\n // We do NOT add edges for the intrinsic wrapper itself, only for inner\n // refs the recursion uncovers (Ref / Fn::GetAtt / Fn::Sub, plus any\n // further-nested Fn::Join / Fn::Select / Fn::Split chain).\n if ('Fn::Join' in obj) {\n const joinValue = obj['Fn::Join'];\n if (Array.isArray(joinValue) && joinValue.length >= 2) {\n // Skip the delimiter (joinValue[0]); recurse into the items array.\n this.extractRefsFromValue(joinValue[1], dependencies);\n }\n return;\n }\n if ('Fn::Select' in obj) {\n const selectValue = obj['Fn::Select'];\n if (Array.isArray(selectValue) && selectValue.length >= 2) {\n // Index may itself be an intrinsic (rare but valid); recurse into both.\n this.extractRefsFromValue(selectValue[0], dependencies);\n this.extractRefsFromValue(selectValue[1], dependencies);\n }\n return;\n }\n if ('Fn::Split' in obj) {\n const splitValue = obj['Fn::Split'];\n if (Array.isArray(splitValue) && splitValue.length >= 2) {\n // Skip the delimiter (splitValue[0]); recurse into the source value.\n this.extractRefsFromValue(splitValue[1], dependencies);\n }\n return;\n }\n\n // Recursively process all values\n Object.values(obj).forEach((v) => this.extractRefsFromValue(v, dependencies));\n }\n\n /**\n * Check if a resource has a specific property\n */\n hasProperty(resource: TemplateResource, propertyPath: string): boolean {\n if (!resource.Properties) {\n return false;\n }\n\n const parts = propertyPath.split('.');\n let current: unknown = resource.Properties;\n\n for (const part of parts) {\n if (typeof current !== 'object' || current === null) {\n return false;\n }\n\n const obj = current as Record<string, unknown>;\n if (!(part in obj)) {\n return false;\n }\n\n current = obj[part];\n }\n\n return true;\n }\n\n /**\n * Get a property value from a resource\n */\n getProperty(resource: TemplateResource, propertyPath: string): unknown {\n if (!resource.Properties) {\n return undefined;\n }\n\n const parts = propertyPath.split('.');\n let current: unknown = resource.Properties;\n\n for (const part of parts) {\n if (typeof current !== 'object' || current === null) {\n return undefined;\n }\n\n const obj = current as Record<string, unknown>;\n if (!(part in obj)) {\n return undefined;\n }\n\n current = obj[part];\n }\n\n return current;\n }\n\n /**\n * Validate template structure\n */\n validateTemplate(template: unknown): template is CloudFormationTemplate {\n if (typeof template !== 'object' || template === null) {\n this.logger.error('Template is not an object');\n return false;\n }\n\n const t = template as Record<string, unknown>;\n\n if (!('Resources' in t)) {\n this.logger.error('Template missing Resources section');\n return false;\n }\n\n if (typeof t['Resources'] !== 'object' || t['Resources'] === null) {\n this.logger.error('Template Resources is not an object');\n return false;\n }\n\n const resources = t['Resources'] as Record<string, unknown>;\n\n // Validate each resource has a Type\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (typeof resource !== 'object' || resource === null) {\n this.logger.error(`Resource ${logicalId} is not an object`);\n return false;\n }\n\n const r = resource as Record<string, unknown>;\n if (!('Type' in r) || typeof r['Type'] !== 'string') {\n this.logger.error(`Resource ${logicalId} missing Type or Type is not a string`);\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Get all resources of a specific type\n */\n getResourcesByType(\n template: CloudFormationTemplate,\n resourceType: string\n ): Map<string, TemplateResource> {\n const resources = new Map<string, TemplateResource>();\n\n for (const [logicalId, resource] of Object.entries(template.Resources)) {\n if (resource.Type === resourceType) {\n resources.set(logicalId, resource);\n }\n }\n\n return resources;\n }\n\n /**\n * Count resources in template\n */\n countResources(template: CloudFormationTemplate): number {\n return Object.keys(template.Resources).length;\n }\n}\n","/**\n * Lambda VpcConfig implicit deletion dependencies.\n *\n * AWS::Lambda::Function with a VpcConfig holds onto an ENI in the configured\n * subnets / security groups for some time AFTER the function is deleted.\n * If we tear down the VPC's Subnets / SecurityGroups before the ENI is fully\n * detached, the EC2 API rejects the delete with \"has dependencies\" /\n * \"DependencyViolation\".\n *\n * The Ref-based dependency expressed by `VpcConfig.SubnetIds: [{ Ref: ... }]`\n * is normally captured by `TemplateParser.extractDependencies` and recorded\n * in `state.dependencies`, which already gives the correct teardown order.\n * This module provides a defense-in-depth, property-based extractor so the\n * ordering still holds when:\n * - state was written by an older cdkd version that did not record the dep\n * - extractDependencies misses a wrapping intrinsic for some reason\n *\n * The returned edges express: \"the Lambda must be deleted BEFORE each\n * referenced Subnet / SecurityGroup\".\n */\nimport type { TemplateResource } from '../types/resource.js';\n\n/** A single dependency edge for the DELETE phase. */\nexport interface DeleteDepEdge {\n /** Logical ID that must be deleted FIRST. */\n before: string;\n /** Logical ID that must be deleted AFTER `before`. */\n after: string;\n}\n\n/**\n * Minimal shape used by extractLambdaVpcDeleteDeps: a logical-ID-keyed map of\n * resources where each entry exposes a CloudFormation-style `Type` and\n * `Properties`. Both `TemplateResource` and the ad-hoc per-stack template\n * built in destroy.ts conform to this.\n */\nexport type ResourceLike = Pick<TemplateResource, 'Type' | 'Properties'>;\n\n/**\n * Extract implicit delete edges for AWS::Lambda::Function with a VpcConfig.\n *\n * For each Lambda function in the input map, scans\n * `Properties.VpcConfig.SubnetIds` and `Properties.VpcConfig.SecurityGroupIds`\n * for `{ Ref: <logicalId> }` / `{ \"Fn::GetAtt\": [<logicalId>, ...] }`\n * references. Every referenced ID that exists in the input map produces an\n * edge `{ before: <lambdaId>, after: <targetId> }`.\n *\n * Notes:\n * - Properties already resolved to physical IDs (state.properties after\n * deploy) yield no edges. That is intentional — in that case the caller\n * should rely on `state.dependencies`, which preserves logical IDs.\n * - Self-edges and edges pointing to absent IDs are filtered out.\n * - Returned edges are de-duplicated.\n */\nexport function extractLambdaVpcDeleteDeps(\n resources: Record<string, ResourceLike>\n): DeleteDepEdge[] {\n const edges: DeleteDepEdge[] = [];\n const seen = new Set<string>();\n\n for (const [lambdaId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::Lambda::Function') continue;\n\n const vpcConfig = (resource.Properties ?? {})['VpcConfig'];\n if (!isObject(vpcConfig)) continue;\n\n const targets = new Set<string>();\n collectRefIds(vpcConfig['SubnetIds'], targets);\n collectRefIds(vpcConfig['SecurityGroupIds'], targets);\n\n for (const targetId of targets) {\n if (targetId === lambdaId) continue;\n if (!(targetId in resources)) continue;\n const key = `${lambdaId}\\u0000${targetId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({ before: lambdaId, after: targetId });\n }\n }\n\n return edges;\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Walk `value` (typically an array) and collect every logical ID referenced\n * via `{ Ref: ... }` or `{ \"Fn::GetAtt\": [<id>, ...] }`. Pseudo parameters\n * (Refs starting with `AWS::`) are skipped.\n */\nfunction collectRefIds(value: unknown, out: Set<string>): void {\n if (value === null || value === undefined) return;\n\n if (Array.isArray(value)) {\n for (const item of value) collectRefIds(item, out);\n return;\n }\n\n if (!isObject(value)) return;\n\n if (typeof value['Ref'] === 'string') {\n const ref = value['Ref'];\n if (!ref.startsWith('AWS::')) out.add(ref);\n return;\n }\n\n if (Array.isArray(value['Fn::GetAtt'])) {\n const arr = value['Fn::GetAtt'];\n if (typeof arr[0] === 'string') out.add(arr[0]);\n return;\n }\n\n // Other intrinsics (Fn::Join, Fn::If, ...) cannot be statically resolved\n // without a full IntrinsicResolver pass; the regular extractDependencies\n // path handles those at deploy time.\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\n\n/**\n * CDK-injected defensive `DependsOn` edges that block deploy parallelization\n * but are not required for AWS API correctness.\n *\n * The CDK constructs eagerly inject `DependsOn` from VPC Lambdas (and adjacent\n * resources — IAM Role / Policy that the Lambda uses, the Lambda::Url that\n * derives its FunctionUrl from the Lambda, the EventSourceMapping that wires\n * the Lambda to a queue) onto the private subnets' `DefaultRoute` /\n * `RouteTableAssociation` so that nothing tries to invoke the Lambda before\n * its egress path to the internet is up. The dependency is real at *runtime*\n * (a Lambda code call to a third-party API can't reach the internet without a\n * NAT route), but it is NOT required at *deploy time* — `CreateFunction` /\n * `CreateFunctionUrlConfig` / `AddPermission` / `CreateEventSourceMapping`\n * all accept a function in `Pending` state and AWS resolves the asynchronous\n * ENI provisioning + route binding in the background. cdkd's existing Custom\n * Resource path already relies on this: the post-`CreateFunction` `State=Active`\n * wait was deliberately moved to `CustomResourceProvider.sendRequest` (the\n * one consumer that breaks against `Pending`) so that VPC Lambdas don't\n * double the deploy time of the average benchmark stack — see\n * `src/provisioning/providers/lambda-function-provider.ts` and PR #121.\n *\n * The cost of leaving this defensive edge in place: a CloudFront Distribution\n * whose Origin is `Lambda::Url.FunctionUrl` cannot start its ~3-min edge\n * propagation until the Lambda finishes, which itself cannot start until the\n * NAT GW is `available` (~2 min). That serialization adds ~5 min to every\n * VPC + Lambda + CloudFront stack. Relaxing the defensive edge collapses\n * the two waits onto one timeline (`max(NAT, CF) ≈ CF`), measured at −45.6%\n * on `bench-cdk-sample` (387s → 211s).\n *\n * The list below is intentionally narrow (`from`-types that the CDK actually\n * decorates with these route DependsOns + `to`-types that are pure egress\n * wiring). It is NOT a general \"ignore all DependsOn\" toggle — Ref / GetAtt\n * edges are untouched, and DependsOn pairs outside this list are also kept.\n */\nconst DEFENSIVE_DEPENDS_ON_TYPE_PAIRS: ReadonlyArray<{\n fromType: string;\n toType: string;\n}> = [\n // VPC Lambda's execution Role (and its inline Policy) get DependsOn'd onto\n // the route only because CDK assumes the Lambda will run before the route\n // is up. The Role/Policy create call itself is VPC-agnostic.\n { fromType: 'AWS::IAM::Role', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::IAM::Role', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n { fromType: 'AWS::IAM::Policy', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::IAM::Policy', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n\n // VPC Lambda itself: CreateFunction returns synchronously while the\n // function is still in Pending; the route only matters once the function\n // is invoked at runtime.\n { fromType: 'AWS::Lambda::Function', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::Lambda::Function', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n\n // Lambda::Url is just a deterministic URL derivation off the function; it\n // doesn't need the function's runtime egress to exist.\n { fromType: 'AWS::Lambda::Url', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::Lambda::Url', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n\n // EventSourceMapping just registers the wire-up; AWS handles delivery\n // async and will retry once the function reaches Active.\n { fromType: 'AWS::Lambda::EventSourceMapping', toType: 'AWS::EC2::Route' },\n {\n fromType: 'AWS::Lambda::EventSourceMapping',\n toType: 'AWS::EC2::SubnetRouteTableAssociation',\n },\n];\n\n/**\n * Compute the set of DependsOn entries on `resource` that fall under one of\n * the CDK-defensive type pairs above. The DAG builder skips these edges\n * when relaxation is enabled.\n *\n * Returns the subset of DependsOn target logical IDs that can be skipped.\n * DependsOn entries that don't match any rule (or that aren't strings, or\n * that point to non-existent resources) are returned untouched (i.e. NOT in\n * the skip set), so they continue to be added to the graph.\n */\nexport function defensiveDependsOnToSkip(\n resource: TemplateResource,\n template: CloudFormationTemplate\n): Set<string> {\n const skip = new Set<string>();\n\n if (!resource.DependsOn) {\n return skip;\n }\n\n const dependsOn = Array.isArray(resource.DependsOn) ? resource.DependsOn : [resource.DependsOn];\n\n for (const dep of dependsOn) {\n if (typeof dep !== 'string') continue;\n const target = template.Resources[dep];\n if (!target) continue;\n const fromType = resource.Type;\n const toType = target.Type;\n if (!fromType || !toType) continue;\n const matched = DEFENSIVE_DEPENDS_ON_TYPE_PAIRS.some(\n (pair) => pair.fromType === fromType && pair.toType === toType\n );\n if (matched) {\n skip.add(dep);\n }\n }\n\n return skip;\n}\n","import graphlib from 'graphlib';\nimport type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { TemplateParser } from './template-parser.js';\nimport { extractLambdaVpcDeleteDeps } from './lambda-vpc-deps.js';\nimport { defensiveDependsOnToSkip } from './cdk-defensive-deps.js';\nimport { getLogger } from '../utils/logger.js';\nimport { DependencyError } from '../utils/error-handler.js';\n\nconst { Graph, alg } = graphlib;\ntype GraphType = graphlib.Graph;\n\nconst IAM_ROLE_POLICY_TYPES: ReadonlySet<string> = new Set([\n 'AWS::IAM::Policy',\n 'AWS::IAM::RolePolicy',\n 'AWS::IAM::ManagedPolicy',\n]);\n\nexport interface DagBuilderOptions {\n /**\n * When true, drop the CDK-injected defensive DependsOn edges that block\n * VPC-Lambda deploys behind NAT route stabilization. Off by default — see\n * `cdk-defensive-deps.ts` for the rationale and the type-pair allowlist.\n */\n relaxCdkVpcDefensiveDeps?: boolean;\n}\n\n/**\n * Dependency graph builder for CloudFormation resources\n *\n * Builds a directed acyclic graph (DAG) of resource dependencies\n * based on Ref, Fn::GetAtt, and DependsOn\n */\nexport class DagBuilder {\n private logger = getLogger().child('DagBuilder');\n private parser = new TemplateParser();\n private options: DagBuilderOptions;\n\n constructor(options: DagBuilderOptions = {}) {\n this.options = options;\n }\n\n /**\n * Build dependency graph from CloudFormation template\n *\n * Creates a directed graph where:\n * - Nodes = resource logical IDs\n * - Edges = dependencies (A -> B means B depends on A)\n */\n buildGraph(template: CloudFormationTemplate): GraphType {\n const graph = new Graph({ directed: true });\n\n this.logger.debug('Building dependency graph...');\n\n // Add all resources as nodes\n const resourceIds = this.parser.getResourceIds(template);\n resourceIds.forEach((logicalId) => {\n const resource = this.parser.getResource(template, logicalId);\n graph.setNode(logicalId, resource);\n this.logger.debug(`Added node: ${logicalId} (${resource?.Type})`);\n });\n\n this.logger.debug(`Total nodes: ${resourceIds.length}`);\n\n // Add edges for dependencies\n let edgeCount = 0;\n let relaxedEdgeCount = 0;\n for (const logicalId of resourceIds) {\n const resource = this.parser.getResource(template, logicalId);\n if (!resource) {\n continue;\n }\n\n const dependencies = this.parser.extractDependencies(resource);\n // When relaxation is enabled, compute the subset of DependsOn entries\n // (NOT Ref / GetAtt — those are real data dependencies) that the CDK\n // injected defensively for runtime egress reasons. Skip them at edge\n // insertion time. See `cdk-defensive-deps.ts` for the type-pair list.\n const skip = this.options.relaxCdkVpcDefensiveDeps\n ? defensiveDependsOnToSkip(resource, template)\n : null;\n\n for (const depId of dependencies) {\n if (skip?.has(depId)) {\n relaxedEdgeCount++;\n this.logger.debug(\n `Skipped CDK-defensive DependsOn edge: ${depId} -> ${logicalId} (default; opt out with --no-aggressive-vpc-parallel)`\n );\n continue;\n }\n // Only add edge if the dependency exists in the template\n if (graph.hasNode(depId)) {\n graph.setEdge(depId, logicalId); // depId -> logicalId (logicalId depends on depId)\n edgeCount++;\n this.logger.debug(`Added edge: ${depId} -> ${logicalId}`);\n } else {\n this.logger.warn(\n `Resource ${logicalId} depends on ${depId}, but ${depId} not found in template`\n );\n }\n }\n }\n if (relaxedEdgeCount > 0) {\n this.logger.info(\n `[DagBuilder] Relaxed ${relaxedEdgeCount} CDK-defensive DependsOn edge(s) (default; opt out with --no-aggressive-vpc-parallel)`\n );\n }\n\n this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);\n\n // Add implicit edges from IAM::Policy (and friends) attached to a Custom\n // Resource's ServiceToken Lambda's execution role.\n // WHY: CloudFormation templates only express deps via Ref/GetAtt/DependsOn.\n // A Custom Resource typically refs only the Lambda (via ServiceToken), not the\n // inline IAM::Policy that grants the Lambda its runtime permissions. Without this\n // edge the Custom Resource can run before the policy attachment API returns, so\n // the handler hits AccessDenied in the middle of deploy.\n edgeCount += this.addCustomResourcePolicyEdges(graph, template);\n\n // Defense-in-depth edges for AWS::Lambda::Function VpcConfig: even though\n // Refs in `Properties.VpcConfig.SubnetIds` / `SecurityGroupIds` are\n // already picked up by extractDependencies (and so will produce edges in\n // the loop above), an explicit pass guards against future regressions in\n // the recursive extractor and makes the Lambda-vs-VPC ordering visible\n // in the DAG even when those properties are wrapped in unusual shapes.\n edgeCount += this.addLambdaVpcEdges(graph, template);\n\n // Validate graph is acyclic\n if (!alg.isAcyclic(graph)) {\n const cycles = this.findCycles(graph);\n throw new DependencyError(\n `Circular dependency detected in template. Cycles: ${cycles.map((c) => c.join(' -> ')).join('; ')}`\n );\n }\n\n return graph;\n }\n\n /**\n * Get execution levels via topological sort\n *\n * Returns resources grouped by execution level:\n * - Level 0: Resources with no dependencies\n * - Level 1: Resources that depend only on Level 0\n * - Level N: Resources that depend on Level 0..N-1\n *\n * Resources in the same level can be executed in parallel.\n */\n getExecutionLevels(graph: GraphType): string[][] {\n const levels: string[][] = [];\n const graphCopy = new Graph({ directed: true });\n\n // Copy the graph\n graph.nodes().forEach((node: string) => {\n graphCopy.setNode(node, graph.node(node));\n });\n graph.edges().forEach((edge: graphlib.Edge) => {\n graphCopy.setEdge(edge.v, edge.w);\n });\n\n this.logger.debug('Computing execution levels...');\n\n let levelNum = 0;\n while (graphCopy.nodeCount() > 0) {\n // Find nodes with no incoming edges (no dependencies)\n const readyNodes = graphCopy.nodes().filter((node) => {\n const predecessors = graphCopy.predecessors(node);\n return !predecessors || predecessors.length === 0;\n });\n\n if (readyNodes.length === 0) {\n // This should not happen if graph is acyclic, but check anyway\n const remaining = graphCopy.nodes();\n throw new DependencyError(\n `Circular dependency detected. Remaining nodes: ${remaining.join(', ')}`\n );\n }\n\n this.logger.debug(\n `Level ${levelNum}: ${readyNodes.length} resources - ${readyNodes.join(', ')}`\n );\n levels.push(readyNodes);\n\n // Remove these nodes from the graph\n readyNodes.forEach((node) => {\n graphCopy.removeNode(node);\n });\n\n levelNum++;\n }\n\n this.logger.debug(`Execution levels computed: ${levels.length} levels`);\n\n return levels;\n }\n\n /**\n * Find all cycles in the graph\n */\n private findCycles(graph: GraphType): string[][] {\n const cycles: string[][] = [];\n const visited = new Set<string>();\n const recursionStack = new Set<string>();\n const path: string[] = [];\n\n const dfs = (node: string): boolean => {\n visited.add(node);\n recursionStack.add(node);\n path.push(node);\n\n const successors = graph.successors(node) || [];\n\n for (const successor of successors) {\n if (!visited.has(successor)) {\n if (dfs(successor)) {\n return true;\n }\n } else if (recursionStack.has(successor)) {\n // Found a cycle\n const cycleStart = path.indexOf(successor);\n const cycle = path.slice(cycleStart);\n cycle.push(successor);\n cycles.push(cycle);\n return true;\n }\n }\n\n path.pop();\n recursionStack.delete(node);\n return false;\n };\n\n for (const node of graph.nodes()) {\n if (!visited.has(node)) {\n dfs(node);\n }\n }\n\n return cycles;\n }\n\n /**\n * Get all dependencies for a resource (transitive)\n */\n getAllDependencies(graph: GraphType, logicalId: string): Set<string> {\n const dependencies = new Set<string>();\n\n const visit = (node: string) => {\n const predecessors = graph.predecessors(node) || [];\n predecessors.forEach((pred: string) => {\n if (!dependencies.has(pred)) {\n dependencies.add(pred);\n visit(pred); // Recursively visit dependencies\n }\n });\n };\n\n visit(logicalId);\n return dependencies;\n }\n\n /**\n * Get all dependents for a resource (transitive)\n */\n getAllDependents(graph: GraphType, logicalId: string): Set<string> {\n const dependents = new Set<string>();\n\n const visit = (node: string) => {\n const successors = graph.successors(node) || [];\n successors.forEach((succ: string) => {\n if (!dependents.has(succ)) {\n dependents.add(succ);\n visit(succ); // Recursively visit dependents\n }\n });\n };\n\n visit(logicalId);\n return dependents;\n }\n\n /**\n * Get direct dependencies for a resource\n */\n getDirectDependencies(graph: GraphType, logicalId: string): string[] {\n return graph.predecessors(logicalId) || [];\n }\n\n /**\n * Get direct dependents for a resource\n */\n getDirectDependents(graph: GraphType, logicalId: string): string[] {\n return graph.successors(logicalId) || [];\n }\n\n /**\n * Check if resource A depends on resource B\n */\n dependsOn(graph: GraphType, resourceA: string, resourceB: string): boolean {\n const deps = this.getAllDependencies(graph, resourceA);\n return deps.has(resourceB);\n }\n\n /**\n * Add implicit edges from IAM::Policy resources to Custom Resources whose\n * ServiceToken Lambda's execution role those policies attach to.\n *\n * Returns the number of edges added.\n */\n private addCustomResourcePolicyEdges(graph: GraphType, template: CloudFormationTemplate): number {\n const rolePolicies = this.buildRolePoliciesMap(template);\n if (rolePolicies.size === 0) {\n return 0;\n }\n\n let added = 0;\n for (const logicalId of this.parser.getResourceIds(template)) {\n const resource = this.parser.getResource(template, logicalId);\n if (!resource || !this.isCustomResourceType(resource.Type)) {\n continue;\n }\n\n const serviceToken = (resource.Properties ?? {})['ServiceToken'];\n const lambdaId = this.extractLogicalIdFromReference(serviceToken);\n if (!lambdaId) continue;\n\n const lambdaResource = this.parser.getResource(template, lambdaId);\n if (!lambdaResource || lambdaResource.Type !== 'AWS::Lambda::Function') {\n continue;\n }\n\n const roleId = this.extractLogicalIdFromReference((lambdaResource.Properties ?? {})['Role']);\n if (!roleId) continue;\n\n const policies = rolePolicies.get(roleId);\n if (!policies) continue;\n\n for (const policyId of policies) {\n if (policyId === logicalId) continue;\n if (!graph.hasNode(policyId)) continue;\n if (graph.hasEdge(policyId, logicalId)) continue;\n graph.setEdge(policyId, logicalId);\n added++;\n this.logger.debug(\n `Added implicit edge (custom resource policy): ${policyId} -> ${logicalId}`\n );\n }\n }\n\n if (added > 0) {\n this.logger.debug(`Added ${added} implicit edges for custom resource policies`);\n }\n return added;\n }\n\n /**\n * Add edges from Subnets / SecurityGroups referenced by an\n * AWS::Lambda::Function VpcConfig to the Lambda itself.\n *\n * Same direction as a normal `Ref`-derived edge (Subnet -> Lambda), so for\n * deploy this just duplicates what extractDependencies already produced.\n * The point is robustness: if a future template massages the VpcConfig\n * shape in a way the recursive extractor doesn't anticipate, this pass\n * still ties the Lambda to its networking resources so that the\n * deletion-time reverse traversal continues to delete Lambda before\n * Subnet / SecurityGroup.\n *\n * Returns the number of NEW edges added (existing edges are skipped).\n */\n private addLambdaVpcEdges(graph: GraphType, template: CloudFormationTemplate): number {\n const edges = extractLambdaVpcDeleteDeps(template.Resources);\n if (edges.length === 0) return 0;\n\n let added = 0;\n for (const edge of edges) {\n // edge: { before: lambdaId, after: vpcResourceId }\n // Edge convention: setEdge(depId, dependentId) means dependentId\n // depends on depId. The Lambda depends on the Subnet / SG, so\n // depId = vpcResourceId (after), dependentId = lambdaId (before).\n const depId = edge.after;\n const dependentId = edge.before;\n if (!graph.hasNode(depId) || !graph.hasNode(dependentId)) continue;\n if (graph.hasEdge(depId, dependentId)) continue;\n graph.setEdge(depId, dependentId);\n added++;\n this.logger.debug(`Added implicit edge (lambda vpc): ${depId} -> ${dependentId}`);\n }\n\n if (added > 0) {\n this.logger.debug(`Added ${added} implicit edges for Lambda VpcConfig`);\n }\n return added;\n }\n\n private isCustomResourceType(type: string): boolean {\n return type === 'AWS::CloudFormation::CustomResource' || type.startsWith('Custom::');\n }\n\n /**\n * Build a map of roleLogicalId -> Set<policyLogicalId> by scanning the\n * template for IAM::Policy / IAM::RolePolicy / IAM::ManagedPolicy resources\n * that attach to a role by Ref/GetAtt.\n */\n private buildRolePoliciesMap(template: CloudFormationTemplate): Map<string, Set<string>> {\n const map = new Map<string, Set<string>>();\n\n for (const [policyId, resource] of Object.entries(template.Resources)) {\n if (!IAM_ROLE_POLICY_TYPES.has(resource.Type)) continue;\n\n for (const roleId of this.extractAttachedRoleIds(resource)) {\n let set = map.get(roleId);\n if (!set) {\n set = new Set();\n map.set(roleId, set);\n }\n set.add(policyId);\n }\n }\n\n return map;\n }\n\n /**\n * Extract the logical IDs of IAM::Role resources that a policy resource\n * attaches to. Supports both `Roles: [Ref]` (IAM::Policy / IAM::ManagedPolicy)\n * and `RoleName: Ref` (IAM::RolePolicy) shapes.\n */\n private extractAttachedRoleIds(resource: TemplateResource): string[] {\n const ids: string[] = [];\n const props = resource.Properties ?? {};\n\n const roles = props['Roles'];\n if (Array.isArray(roles)) {\n for (const entry of roles) {\n const id = this.extractLogicalIdFromReference(entry);\n if (id) ids.push(id);\n }\n }\n\n const roleName = props['RoleName'];\n const roleNameId = this.extractLogicalIdFromReference(roleName);\n if (roleNameId) ids.push(roleNameId);\n\n return ids;\n }\n\n /**\n * Extract a resource logical ID from a direct Ref or Fn::GetAtt expression.\n * Returns undefined for literals or intrinsics we can't statically resolve\n * (Fn::Join, Fn::ImportValue, etc.) — callers should skip in that case.\n */\n private extractLogicalIdFromReference(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const obj = value as Record<string, unknown>;\n\n if ('Ref' in obj && typeof obj['Ref'] === 'string') {\n const ref = obj['Ref'];\n return ref.startsWith('AWS::') ? undefined : ref;\n }\n\n if ('Fn::GetAtt' in obj) {\n const getAtt = obj['Fn::GetAtt'];\n if (Array.isArray(getAtt) && typeof getAtt[0] === 'string') {\n return getAtt[0];\n }\n }\n\n return undefined;\n }\n}\n","/**\n * Replacement rules for AWS resource types\n *\n * Defines which property changes require resource replacement (delete + recreate)\n * vs. in-place updates.\n *\n * Based on CloudFormation update behaviors:\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html\n */\n\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Resource replacement rule\n */\ninterface ReplacementRule {\n /** Properties that always require replacement when changed */\n replacementProperties: Set<string>;\n /** Properties that never require replacement */\n updateableProperties?: Set<string>;\n /** Custom logic for conditional replacement */\n conditionalReplacements?: Map<string, (oldValue: unknown, newValue: unknown) => boolean>;\n}\n\n/**\n * Replacement rules registry\n *\n * Maps resource types to their replacement rules\n */\nexport class ReplacementRulesRegistry {\n private logger = getLogger().child('ReplacementRulesRegistry');\n private rules = new Map<string, ReplacementRule>();\n\n constructor() {\n this.initializeRules();\n }\n\n /**\n * Check if a property change requires replacement\n */\n requiresReplacement(\n resourceType: string,\n propertyPath: string,\n oldValue: unknown,\n newValue: unknown\n ): boolean {\n const rule = this.rules.get(resourceType);\n\n if (!rule) {\n // No specific rule for this resource type\n // Conservative approach: assume replacement may be required\n this.logger.debug(\n `No replacement rule for ${resourceType}, conservatively assuming replacement may be required for ${propertyPath}`\n );\n return false; // Default to updateable for unknown types\n }\n\n // Check if property always requires replacement\n if (rule.replacementProperties.has(propertyPath)) {\n this.logger.debug(`Property ${propertyPath} of ${resourceType} requires replacement`);\n return true;\n }\n\n // Check if property is explicitly updateable\n if (rule.updateableProperties?.has(propertyPath)) {\n return false;\n }\n\n // Check conditional replacements\n if (rule.conditionalReplacements?.has(propertyPath)) {\n const condition = rule.conditionalReplacements.get(propertyPath);\n if (condition) {\n const requires = condition(oldValue, newValue);\n this.logger.debug(\n `Conditional replacement for ${propertyPath} of ${resourceType}: ${requires}`\n );\n return requires;\n }\n }\n\n // If not explicitly defined, assume it's updateable\n return false;\n }\n\n /**\n * Initialize replacement rules for common AWS resource types\n */\n private initializeRules(): void {\n // S3 Bucket\n this.rules.set('AWS::S3::Bucket', {\n replacementProperties: new Set([\n 'BucketName', // Changing bucket name requires replacement\n ]),\n updateableProperties: new Set([\n 'Tags',\n 'VersioningConfiguration',\n 'LifecycleConfiguration',\n 'PublicAccessBlockConfiguration',\n 'BucketEncryption',\n 'LoggingConfiguration',\n 'WebsiteConfiguration',\n 'CorsConfiguration',\n 'NotificationConfiguration',\n ]),\n });\n\n // Lambda Function\n this.rules.set('AWS::Lambda::Function', {\n replacementProperties: new Set([\n 'FunctionName', // Changing function name requires replacement\n ]),\n updateableProperties: new Set([\n 'Code',\n 'Handler',\n 'Runtime',\n 'Description',\n 'Timeout',\n 'MemorySize',\n 'Role',\n 'Environment',\n 'Tags',\n 'VpcConfig',\n 'DeadLetterConfig',\n 'TracingConfig',\n 'Layers',\n 'FileSystemConfigs',\n ]),\n });\n\n // DynamoDB Table\n this.rules.set('AWS::DynamoDB::Table', {\n replacementProperties: new Set([\n 'TableName', // Changing table name requires replacement\n 'KeySchema', // Changing key schema requires replacement\n 'AttributeDefinitions', // Changing attributes (in key) requires replacement\n ]),\n updateableProperties: new Set([\n 'BillingMode',\n 'ProvisionedThroughput',\n 'GlobalSecondaryIndexes',\n 'LocalSecondaryIndexes',\n 'StreamSpecification',\n 'SSESpecification',\n 'Tags',\n 'TimeToLiveSpecification',\n 'PointInTimeRecoverySpecification',\n ]),\n });\n\n // SQS Queue\n this.rules.set('AWS::SQS::Queue', {\n replacementProperties: new Set([\n 'QueueName', // Changing queue name requires replacement\n 'FifoQueue', // Changing FIFO attribute requires replacement\n 'ContentBasedDeduplication', // Only for FIFO queues\n ]),\n updateableProperties: new Set([\n 'DelaySeconds',\n 'MaximumMessageSize',\n 'MessageRetentionPeriod',\n 'ReceiveMessageWaitTimeSeconds',\n 'VisibilityTimeout',\n 'RedrivePolicy',\n 'Tags',\n ]),\n });\n\n // IAM Role\n this.rules.set('AWS::IAM::Role', {\n replacementProperties: new Set([\n 'RoleName', // Changing role name requires replacement\n ]),\n updateableProperties: new Set([\n 'AssumeRolePolicyDocument',\n 'Description',\n 'ManagedPolicyArns',\n 'MaxSessionDuration',\n 'Path',\n 'PermissionsBoundary',\n 'Policies',\n 'Tags',\n ]),\n });\n\n // SNS Topic\n this.rules.set('AWS::SNS::Topic', {\n replacementProperties: new Set([\n 'TopicName', // Changing topic name requires replacement\n ]),\n updateableProperties: new Set(['DisplayName', 'Subscription', 'KmsMasterKeyId', 'Tags']),\n });\n\n // ECR Repository\n this.rules.set('AWS::ECR::Repository', {\n replacementProperties: new Set([\n 'RepositoryName', // Changing repository name requires replacement\n ]),\n updateableProperties: new Set([\n 'ImageScanningConfiguration',\n 'ImageTagMutability',\n 'LifecyclePolicy',\n 'RepositoryPolicyText',\n 'Tags',\n ]),\n });\n\n // CloudWatch Log Group\n this.rules.set('AWS::Logs::LogGroup', {\n replacementProperties: new Set([\n 'LogGroupName', // Changing log group name requires replacement\n ]),\n updateableProperties: new Set(['RetentionInDays', 'KmsKeyId']),\n });\n\n // API Gateway RestApi\n this.rules.set('AWS::ApiGateway::RestApi', {\n replacementProperties: new Set([\n 'Name', // Changing API name can require replacement in some cases\n ]),\n updateableProperties: new Set([\n 'Description',\n 'Policy',\n 'EndpointConfiguration',\n 'BinaryMediaTypes',\n 'MinimumCompressionSize',\n 'Tags',\n ]),\n });\n\n // ECS Task Definition\n this.rules.set('AWS::ECS::TaskDefinition', {\n replacementProperties: new Set([\n // Task definitions are immutable - any change requires replacement\n 'Family',\n 'ContainerDefinitions',\n 'Cpu',\n 'Memory',\n 'NetworkMode',\n 'RequiresCompatibilities',\n 'ExecutionRoleArn',\n 'TaskRoleArn',\n 'Volumes',\n ]),\n });\n\n // Add more resource types as needed\n this.logger.debug(`Initialized replacement rules for ${this.rules.size} resource types`);\n }\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport type {\n StackState,\n ChangeType,\n ResourceChange,\n PropertyChange,\n AttributeChange,\n ResourceState,\n} from '../types/state.js';\nimport { getLogger } from '../utils/logger.js';\nimport { ReplacementRulesRegistry } from './replacement-rules.js';\n\n/**\n * Best-effort resolver for intrinsic functions during diff calculation.\n * Should return the resolved value on success, or the original value if resolution fails.\n * Kept as a callback to avoid circular dependency between analyzer and deployment layers.\n */\nexport type IntrinsicResolveFn = (value: unknown) => Promise<unknown>;\n\n/**\n * Diff calculator for comparing desired state (template) with current state\n */\nexport class DiffCalculator {\n private logger = getLogger().child('DiffCalculator');\n private replacementRules = new ReplacementRulesRegistry();\n\n /**\n * Calculate changes needed to reach desired state\n *\n * @param currentState Current stack state (use existing state or create a new StackState with empty resources for new stacks)\n * @param desiredTemplate Desired CloudFormation template\n * @param resolveFn Optional intrinsic resolver. When provided, desired properties are\n * resolved against current state before comparison so that changes\n * buried inside intrinsics (e.g. `Fn::Join` literal args) are detected.\n * If resolution throws for a given property value, the unresolved\n * value is used (falling back to the original \"assume equal\" behavior).\n * @returns Map of logical ID to resource change\n */\n async calculateDiff(\n currentState: StackState,\n desiredTemplate: CloudFormationTemplate,\n resolveFn?: IntrinsicResolveFn\n ): Promise<Map<string, ResourceChange>> {\n const changes = new Map<string, ResourceChange>();\n\n const currentResources = currentState.resources;\n const desiredResources = desiredTemplate.Resources;\n\n this.logger.debug('Calculating diff...');\n this.logger.debug(`Current resources: ${Object.keys(currentResources).length}`);\n this.logger.debug(`Desired resources: ${Object.keys(desiredResources).length}`);\n\n // Track which resources we've seen\n const processedLogicalIds = new Set<string>();\n\n // Check for CREATE and UPDATE\n for (const [logicalId, desiredResource] of Object.entries(desiredResources)) {\n // Skip CDK metadata resources (they don't actually deploy anything)\n if (desiredResource.Type === 'AWS::CDK::Metadata') {\n this.logger.debug(`Skipping metadata resource: ${logicalId}`);\n processedLogicalIds.add(logicalId);\n continue;\n }\n\n processedLogicalIds.add(logicalId);\n\n const currentResource = currentResources[logicalId];\n\n if (!currentResource) {\n // Resource doesn't exist in current state -> CREATE\n changes.set(logicalId, {\n logicalId,\n changeType: 'CREATE',\n resourceType: desiredResource.Type,\n desiredProperties: desiredResource.Properties || {},\n });\n this.logger.debug(`CREATE: ${logicalId} (${desiredResource.Type})`);\n } else if (currentResource.resourceType !== desiredResource.Type) {\n // Resource type changed -> requires replacement (DELETE + CREATE)\n // For simplicity, we'll mark this as UPDATE with requiresReplacement\n const propertyChanges: PropertyChange[] = [\n {\n path: 'Type',\n oldValue: currentResource.resourceType,\n newValue: desiredResource.Type,\n requiresReplacement: true,\n },\n ];\n\n changes.set(logicalId, {\n logicalId,\n changeType: 'UPDATE',\n resourceType: desiredResource.Type,\n currentProperties: currentResource.properties,\n desiredProperties: desiredResource.Properties || {},\n propertyChanges,\n });\n this.logger.debug(\n `UPDATE (Type change): ${logicalId} (${currentResource.resourceType} -> ${desiredResource.Type})`\n );\n } else {\n // Resource exists with same type -> check properties.\n //\n // State stores already-resolved values (e.g. \"my-bucket-value\"), while the\n // template holds unresolved intrinsics (e.g. { \"Fn::Join\": [...] }). When an\n // intrinsic wraps literal content that changed (e.g. \"-value\" -> \"-value2\"),\n // a naive comparison would short-circuit on the intrinsic node and miss the\n // change. Resolving desired props against current state first avoids that.\n const rawDesiredProps = desiredResource.Properties || {};\n const desiredPropsForCompare = resolveFn\n ? await this.resolveBestEffort(rawDesiredProps, resolveFn)\n : rawDesiredProps;\n\n const propertyChanges = this.compareProperties(\n desiredResource.Type,\n currentResource.properties,\n desiredPropsForCompare\n );\n\n // Schema v5+ template-attribute diff: `DeletionPolicy` /\n // `UpdateReplacePolicy` may change without any property change. cdkd\n // pre-v5 silently reported `No changes detected` for those, so a\n // user who removed `RemovalPolicy.DESTROY` from their CDK code saw\n // nothing happen on the next deploy. Detect them here too so the\n // attribute flip is surfaced (and the deploy engine refreshes the\n // value in state).\n const attributeChanges = this.compareAttributes(currentResource, desiredResource);\n\n if (propertyChanges.length > 0 || attributeChanges.length > 0) {\n // Property and/or attribute changed -> UPDATE\n changes.set(logicalId, {\n logicalId,\n changeType: 'UPDATE',\n resourceType: desiredResource.Type,\n currentProperties: currentResource.properties,\n desiredProperties: rawDesiredProps,\n propertyChanges,\n ...(attributeChanges.length > 0 && { attributeChanges }),\n });\n this.logger.debug(\n `UPDATE: ${logicalId} (${propertyChanges.length} property changes, ${attributeChanges.length} attribute changes)`\n );\n } else {\n // No changes -> NO_CHANGE\n changes.set(logicalId, {\n logicalId,\n changeType: 'NO_CHANGE',\n resourceType: desiredResource.Type,\n currentProperties: currentResource.properties,\n desiredProperties: rawDesiredProps,\n });\n this.logger.debug(`NO_CHANGE: ${logicalId}`);\n }\n }\n }\n\n // Check for DELETE (resources in current state but not in desired template)\n for (const [logicalId, currentResource] of Object.entries(currentResources)) {\n if (!processedLogicalIds.has(logicalId)) {\n changes.set(logicalId, {\n logicalId,\n changeType: 'DELETE',\n resourceType: currentResource.resourceType,\n currentProperties: currentResource.properties,\n });\n this.logger.debug(`DELETE: ${logicalId} (${currentResource.resourceType})`);\n }\n }\n\n const summary = this.getSummary(changes);\n this.logger.debug(\n `Diff calculated: ${summary.create} CREATE, ${summary.update} UPDATE, ${summary.delete} DELETE, ${summary.noChange} NO_CHANGE`\n );\n\n return changes;\n }\n\n /**\n * Best-effort resolution of template property intrinsics against current state.\n *\n * Iterates top-level properties and resolves each independently: if resolution\n * throws (e.g. Ref to a resource that isn't in state yet), the original value\n * is kept so downstream comparison falls back to the \"assume intrinsic equals\n * anything\" behavior for that one value instead of failing the whole diff.\n */\n private async resolveBestEffort(\n properties: Record<string, unknown>,\n resolveFn: IntrinsicResolveFn\n ): Promise<Record<string, unknown>> {\n const resolved: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n try {\n resolved[key] = await resolveFn(value);\n } catch {\n resolved[key] = value;\n }\n }\n return resolved;\n }\n\n /**\n * Compare CloudFormation template-level attributes (`DeletionPolicy`,\n * `UpdateReplacePolicy`) between cdkd state and the synth template.\n *\n * Schema v5+ records these in `ResourceState`; state written by an older\n * cdkd binary has the fields undefined. Treating `undefined === undefined`\n * as \"no change\" means the first post-upgrade deploy of an unchanged\n * template doesn't spuriously fire an attribute diff.\n */\n private compareAttributes(\n currentResource: ResourceState,\n desiredResource: TemplateResource\n ): AttributeChange[] {\n const changes: AttributeChange[] = [];\n if (currentResource.deletionPolicy !== desiredResource.DeletionPolicy) {\n changes.push({\n attribute: 'DeletionPolicy',\n oldValue: currentResource.deletionPolicy,\n newValue: desiredResource.DeletionPolicy,\n });\n }\n if (currentResource.updateReplacePolicy !== desiredResource.UpdateReplacePolicy) {\n changes.push({\n attribute: 'UpdateReplacePolicy',\n oldValue: currentResource.updateReplacePolicy,\n newValue: desiredResource.UpdateReplacePolicy,\n });\n }\n return changes;\n }\n\n /**\n * Compare properties and return list of changes\n *\n * Uses ReplacementRulesRegistry to determine which property changes require replacement.\n * Reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html\n */\n private compareProperties(\n resourceType: string,\n currentProperties: Record<string, unknown>,\n desiredProperties: Record<string, unknown>\n ): PropertyChange[] {\n const changes: PropertyChange[] = [];\n\n // Get all property keys\n const allKeys = new Set([...Object.keys(currentProperties), ...Object.keys(desiredProperties)]);\n\n // Properties to ignore in diff (non-deterministic, changes on every synth)\n const ignoredProperties = new Set<string>();\n if (\n resourceType === 'AWS::CloudFormation::CustomResource' ||\n resourceType.startsWith('Custom::')\n ) {\n ignoredProperties.add('Timestamp');\n }\n\n for (const key of allKeys) {\n if (ignoredProperties.has(key)) continue;\n\n const oldValue = currentProperties[key];\n const newValue = desiredProperties[key];\n\n if (!this.valuesEqual(oldValue, newValue)) {\n // Check if this property change requires replacement\n const requiresReplacement = this.replacementRules.requiresReplacement(\n resourceType,\n key,\n oldValue,\n newValue\n );\n\n changes.push({\n path: key,\n oldValue,\n newValue,\n requiresReplacement,\n });\n\n if (requiresReplacement) {\n this.logger.debug(\n `Property ${key} of ${resourceType} requires replacement (${JSON.stringify(oldValue)} -> ${JSON.stringify(newValue)})`\n );\n }\n }\n }\n\n return changes;\n }\n\n private static readonly INTRINSIC_KEYS = new Set([\n 'Ref',\n 'Fn::Sub',\n 'Fn::GetAtt',\n 'Fn::Join',\n 'Fn::Select',\n 'Fn::Split',\n 'Fn::If',\n 'Fn::ImportValue',\n 'Fn::FindInMap',\n 'Fn::Base64',\n 'Fn::GetAZs',\n 'Fn::Equals',\n 'Fn::And',\n 'Fn::Or',\n 'Fn::Not',\n ]);\n\n /**\n * Check if a value is itself a CloudFormation intrinsic function.\n * e.g. { \"Ref\": \"MyResource\" } or { \"Fn::GetAtt\": [\"Res\", \"Arn\"] }\n * Does NOT match objects that merely contain intrinsics as nested children.\n */\n private static isIntrinsic(value: unknown): boolean {\n if (\n value === null ||\n value === undefined ||\n typeof value !== 'object' ||\n Array.isArray(value)\n ) {\n return false;\n }\n const keys = Object.keys(value as Record<string, unknown>);\n return keys.length === 1 && DiffCalculator.INTRINSIC_KEYS.has(keys[0]!);\n }\n\n /**\n * Deep equality check for values\n *\n * When comparing state (resolved values) with template (unresolved intrinsics),\n * treats intrinsic function nodes as \"not comparable\" and assumes equal.\n * This check happens at each level of recursion, so only the specific value\n * that IS an intrinsic gets skipped — sibling values are still compared normally.\n *\n * Example: { Variables: { AZURE_REGION: \"japaneast\", SECRET_NAME: { \"Fn::Join\": ... } } }\n * - AZURE_REGION: compared normally (string vs string)\n * - SECRET_NAME: one side is intrinsic → treated as equal (skip)\n */\n private valuesEqual(a: unknown, b: unknown): boolean {\n // Strict equality check\n if (a === b) {\n return true;\n }\n\n // Null/undefined check\n if (a == null || b == null) {\n return a === b;\n }\n\n // If either side is an intrinsic function node, we can't compare\n // (state has resolved value like \"arn:...\", template has { \"Fn::GetAtt\": [...] })\n if (DiffCalculator.isIntrinsic(a) || DiffCalculator.isIntrinsic(b)) {\n return true;\n }\n\n // Array check\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) {\n return false;\n }\n return a.every((val, index) => this.valuesEqual(val, b[index]));\n }\n\n // Object check — recurse into each key so intrinsics are detected per-value\n if (typeof a === 'object' && typeof b === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n\n const bKeys = Object.keys(bObj);\n\n // Check keys in new (template) side exist in old (state) side with equal values.\n // Keys only in old side are ignored — they are typically AWS-added defaults\n // (e.g., IncludeCookies, Enabled, Prefix in CloudFront Logging) that don't\n // appear in the template but get stored in state after deployment.\n // Keys only in new side are real additions and will cause inequality.\n for (const key of bKeys) {\n if (!(key in aObj)) {\n return false; // New key added in template\n }\n if (!this.valuesEqual(aObj[key], bObj[key])) {\n return false;\n }\n }\n return true;\n }\n\n // Primitive types\n return false;\n }\n\n /**\n * Get summary of changes\n */\n getSummary(changes: Map<string, ResourceChange>): {\n create: number;\n update: number;\n delete: number;\n noChange: number;\n total: number;\n } {\n const summary = {\n create: 0,\n update: 0,\n delete: 0,\n noChange: 0,\n total: changes.size,\n };\n\n for (const change of changes.values()) {\n switch (change.changeType) {\n case 'CREATE':\n summary.create++;\n break;\n case 'UPDATE':\n summary.update++;\n break;\n case 'DELETE':\n summary.delete++;\n break;\n case 'NO_CHANGE':\n summary.noChange++;\n break;\n }\n }\n\n return summary;\n }\n\n /**\n * Filter changes by type\n */\n filterByType(changes: Map<string, ResourceChange>, type: ChangeType): ResourceChange[] {\n return Array.from(changes.values()).filter((change) => change.changeType === type);\n }\n\n /**\n * Check if there are any changes\n */\n hasChanges(changes: Map<string, ResourceChange>): boolean {\n return Array.from(changes.values()).some((change) => change.changeType !== 'NO_CHANGE');\n }\n\n /**\n * Get changes that require replacement\n */\n getReplacementChanges(changes: Map<string, ResourceChange>): ResourceChange[] {\n return Array.from(changes.values()).filter(\n (change) =>\n change.changeType === 'UPDATE' &&\n change.propertyChanges?.some((pc) => pc.requiresReplacement)\n );\n }\n}\n","import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';\nimport {\n DescribeAvailabilityZonesCommand,\n DescribeLaunchTemplatesCommand,\n} from '@aws-sdk/client-ec2';\nimport { GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';\nimport { GetParameterCommand } from '@aws-sdk/client-ssm';\nimport { getLogger } from '../utils/logger.js';\nimport { getAwsClients } from '../utils/aws-clients.js';\nimport { stringifyValue } from '../utils/stringify.js';\nimport type { CloudFormationTemplate } from '../types/resource.js';\nimport type { ResourceState, StateImportEntry } from '../types/state.js';\nimport type { S3StateBackend } from '../state/s3-state-backend.js';\nimport type { ExportIndexStore } from '../state/export-index-store.js';\n\n/**\n * Special symbol to represent AWS::NoValue\n *\n * When a property resolves to this symbol, it should be removed from the object.\n * This is used for conditional property omission in CloudFormation templates.\n */\nexport const AWS_NO_VALUE = Symbol('AWS::NoValue');\n\n/**\n * Resolver context for intrinsic functions\n */\nexport interface ResolverContext {\n /** Template being processed */\n template: CloudFormationTemplate;\n /** Current resource states (for Ref/GetAtt) */\n resources: Record<string, ResourceState>;\n /** Parameter values (for Ref to parameters) */\n parameters?: Record<string, unknown>;\n /** Evaluated condition values (for Fn::If) */\n conditions?: Record<string, boolean>;\n /** State backend for cross-stack references (Fn::ImportValue) */\n stateBackend?: S3StateBackend;\n /** Current stack name (for Fn::ImportValue to avoid self-reference) */\n stackName?: string;\n /**\n * Persistent exports index for fast `Fn::ImportValue` resolution. When\n * supplied, the resolver tries an O(1) index lookup before falling back\n * to the per-stack state.json scan. Optional for backwards compat; the\n * scan-only path is still correct.\n */\n exportIndex?: ExportIndexStore;\n /**\n * Bag for the resolver to push every successful `Fn::ImportValue`\n * resolution into. The deploy engine reads this after resource\n * provisioning and persists it to the consumer's `state.imports`\n * field (schema v4) so destroy-time strong-reference checks can\n * refuse to delete a producer with active consumers.\n *\n * `Fn::GetStackOutput` does NOT push entries here by design — it is\n * a weak reference (see CLAUDE.md \"Behavior vs CDK\").\n */\n recordedImports?: StateImportEntry[];\n}\n\n/**\n * CloudFormation Intrinsic Function Resolver\n *\n * Resolves CloudFormation intrinsic functions in template values before\n * sending them to Cloud Control API or SDK providers.\n *\n * Supported functions:\n * - Ref (resources and parameters)\n * - Fn::GetAtt\n * - Fn::Join\n * - Fn::Sub\n * - Fn::Select\n * - Fn::Split\n * - Fn::If (Conditions)\n * - Fn::Equals\n * - Fn::And (logical AND)\n * - Fn::Or (logical OR)\n * - Fn::Not (logical NOT)\n * - Fn::ImportValue (cross-stack references)\n * - Fn::GetStackOutput (cross-stack/cross-region output reference)\n * - Fn::FindInMap (mapping lookups)\n * - Fn::Base64 (base64 encoding)\n * - Fn::GetAZs (availability zone listing)\n * - Fn::Cidr (CIDR address block calculation)\n */\n/**\n * AWS Account information cache\n */\ninterface AwsAccountInfo {\n accountId: string;\n region: string;\n partition: string;\n}\n\nlet cachedAccountInfo: AwsAccountInfo | null = null;\n\n/**\n * Cache for availability zones per region\n */\nconst cachedAvailabilityZones: Record<string, string[]> = {};\n\n/**\n * Cache for resolved dynamic references (secretsmanager, ssm)\n */\nconst cachedDynamicReferences: Record<string, string> = {};\n\n/**\n * Get AWS account information from STS\n */\nexport async function getAccountInfo(overrideRegion?: string): Promise<AwsAccountInfo> {\n if (cachedAccountInfo) {\n // If an override region is provided, return with that region\n if (overrideRegion && overrideRegion !== cachedAccountInfo.region) {\n return { ...cachedAccountInfo, region: overrideRegion };\n }\n return cachedAccountInfo;\n }\n\n const logger = getLogger().child('IntrinsicFunctionResolver');\n const awsClients = getAwsClients();\n const stsClient = awsClients.sts;\n\n try {\n const response = await stsClient.send(new GetCallerIdentityCommand({}));\n const accountId = response.Account || '123456789012';\n const region = overrideRegion || process.env['AWS_REGION'] || 'us-east-1';\n const partition = 'aws'; // Could be aws-cn, aws-us-gov, etc.\n\n cachedAccountInfo = { accountId, region, partition };\n logger.debug(`Retrieved AWS account info: ${accountId}, ${region}, ${partition}`);\n // Return with override if different from cached\n if (overrideRegion && overrideRegion !== region) {\n return { ...cachedAccountInfo, region: overrideRegion };\n }\n return cachedAccountInfo;\n } catch (error) {\n logger.warn(\n `Failed to get AWS account info from STS: ${error instanceof Error ? error.message : String(error)}, using defaults`\n );\n // Fallback to environment variables or defaults\n cachedAccountInfo = {\n accountId: process.env['AWS_ACCOUNT_ID'] || '123456789012',\n region: overrideRegion || process.env['AWS_REGION'] || 'us-east-1',\n partition: 'aws',\n };\n return cachedAccountInfo;\n }\n}\n\n/**\n * Reset cached account info (useful for testing)\n */\nexport function resetAccountInfoCache(): void {\n cachedAccountInfo = null;\n // Also reset AZ cache\n for (const key of Object.keys(cachedAvailabilityZones)) {\n delete cachedAvailabilityZones[key];\n }\n // Also reset dynamic reference cache\n for (const key of Object.keys(cachedDynamicReferences)) {\n delete cachedDynamicReferences[key];\n }\n}\n\n/**\n * CloudFormation Parameter definition\n */\nexport interface ParameterDefinition {\n Type: string;\n Default?: unknown;\n AllowedValues?: unknown[];\n AllowedPattern?: string;\n MinLength?: number;\n MaxLength?: number;\n MinValue?: number;\n MaxValue?: number;\n Description?: string;\n ConstraintDescription?: string;\n NoEcho?: boolean;\n}\n\nexport class IntrinsicFunctionResolver {\n private logger = getLogger().child('IntrinsicFunctionResolver');\n private readonly resolverRegion: string;\n\n constructor(region?: string) {\n this.resolverRegion = region || process.env['AWS_REGION'] || 'us-east-1';\n }\n\n /**\n * Resolve parameter values from template Parameters section\n *\n * Merges default values from template with user-provided parameter values.\n * User-provided values take precedence over defaults.\n *\n * @param template CloudFormation template containing Parameters section\n * @param userParameters User-provided parameter values (e.g., from CLI)\n * @returns Record of parameter names to resolved values\n */\n async resolveParameters(\n template: CloudFormationTemplate,\n userParameters?: Record<string, string>\n ): Promise<Record<string, unknown>> {\n const parameters: Record<string, unknown> = {};\n const templateParameters = template.Parameters;\n\n if (!templateParameters || typeof templateParameters !== 'object') {\n return parameters;\n }\n\n for (const [name, definition] of Object.entries(templateParameters)) {\n const paramDef = definition as ParameterDefinition;\n\n // User-provided value takes precedence\n if (userParameters && name in userParameters) {\n const userValue = userParameters[name];\n if (userValue !== undefined) {\n parameters[name] = this.coerceParameterValue(userValue, paramDef.Type);\n this.logger.debug(`Parameter ${name}: using user-provided value ${userValue}`);\n continue;\n }\n }\n\n // Use default value if available\n if ('Default' in paramDef) {\n // SSM Parameter type: resolve the default value (SSM parameter path) via SSM API\n if (paramDef.Type.startsWith('AWS::SSM::Parameter::Value')) {\n const ssmPath = String(paramDef.Default);\n this.logger.debug(`Parameter ${name}: resolving SSM parameter path ${ssmPath}`);\n const resolved = await this.resolveSSMParameter(ssmPath);\n parameters[name] = resolved;\n this.logger.debug(`Parameter ${name}: resolved SSM value ${resolved}`);\n continue;\n }\n\n parameters[name] = paramDef.Default;\n this.logger.debug(\n `Parameter ${name}: using default value ${stringifyValue(paramDef.Default)}`\n );\n continue;\n }\n\n // No value provided and no default - this is an error\n throw new Error(\n `Parameter ${name} is required but no value was provided and no default exists`\n );\n }\n\n return parameters;\n }\n\n /**\n * Resolve an SSM Parameter Store path to its actual value.\n * Used for parameters with type AWS::SSM::Parameter::Value<...>.\n */\n private async resolveSSMParameter(parameterName: string): Promise<string> {\n const client = getAwsClients().ssm;\n const response = await client.send(new GetParameterCommand({ Name: parameterName }));\n return response.Parameter?.Value ?? '';\n }\n\n /**\n * Coerce parameter value to the correct type based on parameter definition\n */\n private coerceParameterValue(value: string, type: string): unknown {\n switch (type) {\n case 'Number':\n return Number(value);\n case 'List<Number>':\n return value.split(',').map((v) => Number(v.trim()));\n case 'CommaDelimitedList':\n return value.split(',').map((v) => v.trim());\n case 'String':\n default:\n return value;\n }\n }\n\n /**\n * Resolve all intrinsic functions in a value\n */\n async resolve(value: unknown, context: ResolverContext): Promise<unknown> {\n return await this.resolveValue(value, context);\n }\n\n /**\n * Evaluate all conditions in the template\n *\n * Conditions are defined in the Conditions section of the CloudFormation template\n * and can reference parameters and pseudo parameters\n */\n async evaluateConditions(context: ResolverContext): Promise<Record<string, boolean>> {\n const conditions: Record<string, boolean> = {};\n const templateConditions = context.template.Conditions;\n\n if (!templateConditions || typeof templateConditions !== 'object') {\n return conditions;\n }\n\n // Evaluate each condition\n for (const [name, definition] of Object.entries(templateConditions)) {\n try {\n const result = await this.resolveValue(definition, context);\n conditions[name] = Boolean(result);\n this.logger.debug(`Evaluated condition ${name} = ${conditions[name]}`);\n } catch (error) {\n this.logger.warn(\n `Failed to evaluate condition ${name}: ${error instanceof Error ? error.message : String(error)}, assuming false`\n );\n conditions[name] = false;\n }\n }\n\n return conditions;\n }\n\n /**\n * Recursively resolve a value\n */\n private async resolveValue(value: unknown, context: ResolverContext): Promise<unknown> {\n // Primitives: return as-is (but check strings for dynamic references)\n if (typeof value !== 'object' || value === null) {\n if (typeof value === 'string' && value.includes('{{resolve:')) {\n return await this.resolveDynamicReferences(value);\n }\n return value;\n }\n\n // Arrays: resolve each element, filtering out AWS::NoValue\n if (Array.isArray(value)) {\n const resolved = await Promise.all(value.map((v) => this.resolveValue(v, context)));\n return resolved.filter((v) => v !== AWS_NO_VALUE);\n }\n\n const obj = value as Record<string, unknown>;\n\n // Check for intrinsic functions\n if ('Ref' in obj) {\n return await this.resolveRef(obj['Ref'] as string, context);\n }\n\n if ('Fn::GetAtt' in obj) {\n return await this.resolveGetAtt(obj['Fn::GetAtt'] as [string, string] | string, context);\n }\n\n if ('Fn::Join' in obj) {\n return await this.resolveJoin(obj['Fn::Join'] as [string, unknown[]], context);\n }\n\n if ('Fn::Sub' in obj) {\n return await this.resolveSub(\n obj['Fn::Sub'] as string | [string, Record<string, unknown>],\n context\n );\n }\n\n if ('Fn::Select' in obj) {\n return await this.resolveSelect(obj['Fn::Select'] as [number, unknown[]], context);\n }\n\n if ('Fn::Split' in obj) {\n return await this.resolveSplit(obj['Fn::Split'] as [string, unknown], context);\n }\n\n if ('Fn::If' in obj) {\n return await this.resolveIf(obj['Fn::If'] as [string, unknown, unknown], context);\n }\n\n if ('Fn::Equals' in obj) {\n return await this.resolveEquals(obj['Fn::Equals'] as [unknown, unknown], context);\n }\n\n if ('Fn::And' in obj) {\n return await this.resolveAnd(obj['Fn::And'] as unknown[], context);\n }\n\n if ('Fn::Or' in obj) {\n return await this.resolveOr(obj['Fn::Or'] as unknown[], context);\n }\n\n if ('Fn::Not' in obj) {\n return await this.resolveNot(obj['Fn::Not'] as [unknown], context);\n }\n\n if ('Fn::ImportValue' in obj) {\n return await this.resolveImportValue(obj['Fn::ImportValue'], context);\n }\n\n if ('Fn::GetStackOutput' in obj) {\n return await this.resolveGetStackOutput(obj['Fn::GetStackOutput'], context);\n }\n\n if ('Fn::FindInMap' in obj) {\n return await this.resolveFindInMap(\n obj['Fn::FindInMap'] as [unknown, unknown, unknown],\n context\n );\n }\n\n if ('Fn::Base64' in obj) {\n return await this.resolveBase64(obj['Fn::Base64'], context);\n }\n\n if ('Fn::GetAZs' in obj) {\n return await this.resolveGetAZs(obj['Fn::GetAZs'], context);\n }\n\n if ('Fn::Cidr' in obj) {\n return await this.resolveCidr(obj['Fn::Cidr'] as [unknown, unknown, unknown], context);\n }\n\n // Not an intrinsic function: recursively resolve object properties\n const resolved: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n const resolvedVal = await this.resolveValue(val, context);\n // Skip properties that resolve to AWS::NoValue\n if (resolvedVal !== AWS_NO_VALUE) {\n resolved[key] = resolvedVal;\n } else {\n this.logger.debug(`Property ${key} resolved to AWS::NoValue, omitting from object`);\n }\n }\n return resolved;\n }\n\n /**\n * Resolve Ref intrinsic function\n *\n * Ref can reference:\n * 1. Resources (returns physical ID)\n * 2. Parameters (returns parameter value)\n * 3. Pseudo parameters (AWS::Region, AWS::AccountId, etc.)\n */\n private async resolveRef(logicalId: string, context: ResolverContext): Promise<unknown> {\n // Check if it's a resource\n const resource = context.resources[logicalId];\n if (resource) {\n this.logger.debug(`Resolved Ref to resource: ${logicalId} -> ${resource.physicalId}`);\n return resource.physicalId;\n }\n\n // Check if it's a parameter\n if (context.parameters && logicalId in context.parameters) {\n const value = context.parameters[logicalId];\n this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${stringifyValue(value)}`);\n return value;\n }\n\n // Check if it's a pseudo parameter\n const pseudoValue = await this.resolvePseudoParameter(logicalId, context);\n if (pseudoValue !== undefined) {\n const valueStr =\n typeof pseudoValue === 'symbol' ? pseudoValue.toString() : String(pseudoValue);\n this.logger.debug(`Resolved Ref to pseudo parameter: ${logicalId} -> ${valueStr}`);\n return pseudoValue;\n }\n\n // Not found\n this.logger.warn(`Ref ${logicalId} not found (not a resource, parameter, or pseudo parameter)`);\n throw new Error(`Ref ${logicalId} not found`);\n }\n\n /**\n * Resolve Fn::GetAtt intrinsic function\n */\n private async resolveGetAtt(\n getAtt: [string, string] | string,\n context: ResolverContext\n ): Promise<unknown> {\n // Fn::GetAtt can be either [LogicalId, AttributeName] or \"LogicalId.AttributeName\"\n let logicalId: string;\n let attributeName: string;\n\n if (Array.isArray(getAtt)) {\n [logicalId, attributeName] = getAtt;\n } else {\n const parts = getAtt.split('.');\n if (parts.length !== 2) {\n throw new Error(`Invalid Fn::GetAtt format: ${getAtt}`);\n }\n [logicalId, attributeName] = parts as [string, string];\n }\n\n const resource = context.resources[logicalId];\n if (!resource) {\n throw new Error(`Resource ${logicalId} not found for Fn::GetAtt`);\n }\n\n // Check if attribute exists in resource.attributes\n // For VPC Ipv6CidrBlocks, always use constructAttribute (dynamic fetch with retry)\n // because the stored value may be stale (empty array from before VPCCidrBlock association)\n const skipCachedAttribute =\n resource.resourceType === 'AWS::EC2::VPC' && attributeName === 'Ipv6CidrBlocks';\n\n if (!skipCachedAttribute && resource.attributes?.[attributeName] !== undefined) {\n const value = resource.attributes[attributeName];\n this.logger.debug(\n `Resolved Fn::GetAtt from attributes: ${logicalId}.${attributeName} -> ${stringifyValue(value)}`\n );\n return value;\n }\n\n // Construct attribute value based on resource type\n const value = await this.constructAttribute(resource, attributeName, context);\n this.logger.debug(\n `Resolved Fn::GetAtt: ${logicalId}.${attributeName} -> ${stringifyValue(value)}`\n );\n return value;\n }\n\n /**\n * Construct resource attribute value based on resource type\n *\n * Many CloudFormation attributes are not returned by Cloud Control API,\n * so we need to construct them manually.\n */\n private async constructAttribute(\n resource: ResourceState,\n attributeName: string,\n _context: ResolverContext\n ): Promise<unknown> {\n const { resourceType, physicalId } = resource;\n const accountInfo = await getAccountInfo(this.resolverRegion);\n const { region, accountId, partition } = accountInfo;\n\n // DynamoDB Table / GlobalTable (CDK TableV2 synthesizes as AWS::DynamoDB::GlobalTable; ARN format is identical)\n if (resourceType === 'AWS::DynamoDB::Table' || resourceType === 'AWS::DynamoDB::GlobalTable') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:dynamodb:${region}:${accountId}:table/${physicalId}`;\n case 'StreamArn':\n // Stream ARN would need to be fetched from API\n return undefined;\n default:\n return physicalId;\n }\n }\n\n // S3 Bucket\n if (resourceType === 'AWS::S3::Bucket') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:s3:::${physicalId}`;\n case 'DomainName':\n return `${physicalId}.s3.amazonaws.com`;\n case 'RegionalDomainName':\n return `${physicalId}.s3.${region}.amazonaws.com`;\n case 'WebsiteURL':\n return `http://${physicalId}.s3-website-${region}.amazonaws.com`;\n default:\n return physicalId;\n }\n }\n\n // IAM Role\n if (resourceType === 'AWS::IAM::Role') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:role/${physicalId}`;\n case 'RoleId':\n // Role ID would need to be fetched from API\n return undefined;\n default:\n return physicalId;\n }\n }\n\n // EC2 VPC - dynamic attributes (IPv6 CIDR requires DescribeVpcs after VPCCidrBlock association)\n if (resourceType === 'AWS::EC2::VPC') {\n switch (attributeName) {\n case 'VpcId':\n return physicalId;\n case 'CidrBlock':\n return resource.attributes?.['CidrBlock'] || resource.properties?.['CidrBlock'];\n case 'Ipv6CidrBlocks': {\n // Must fetch dynamically - IPv6 CIDR is added by VPCCidrBlock resource after VPC creation.\n // After CC API reports VPCCidrBlock CREATE success, the CIDR may still be in\n // 'associating' state. Retry up to 30s waiting for 'associated'.\n try {\n const { EC2Client, DescribeVpcsCommand } = await import('@aws-sdk/client-ec2');\n const ec2 = new EC2Client({ region: this.resolverRegion });\n const maxAttempts = 15;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const resp = await ec2.send(new DescribeVpcsCommand({ VpcIds: [physicalId] }));\n const associations = resp.Vpcs?.[0]?.Ipv6CidrBlockAssociationSet || [];\n const blocks = associations\n .filter((a) => a.Ipv6CidrBlockState?.State === 'associated')\n .map((a) => a.Ipv6CidrBlock);\n if (blocks.length > 0) {\n this.logger.debug(\n `Resolved VPC Ipv6CidrBlocks for ${physicalId}: ${JSON.stringify(blocks)}`\n );\n return blocks;\n }\n // Check if there are any associating CIDRs — if so, wait and retry\n const associating = associations.filter(\n (a) => a.Ipv6CidrBlockState?.State === 'associating'\n );\n if (associating.length === 0) {\n // No IPv6 CIDRs at all\n this.logger.debug(`No IPv6 CIDR associations found for VPC ${physicalId}`);\n return [];\n }\n this.logger.debug(\n `VPC ${physicalId} IPv6 CIDR still associating (attempt ${attempt}/${maxAttempts}), waiting...`\n );\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n this.logger.warn(\n `VPC ${physicalId} IPv6 CIDR did not reach 'associated' state after ${maxAttempts} attempts`\n );\n return [];\n } catch (error) {\n this.logger.warn(\n `Failed to fetch VPC Ipv6CidrBlocks for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n return [];\n }\n }\n case 'DefaultSecurityGroup':\n return resource.attributes?.['DefaultSecurityGroup'] || physicalId;\n default:\n return physicalId;\n }\n }\n\n // IAM Policy\n if (resourceType === 'AWS::IAM::Policy') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:policy/${physicalId}`;\n case 'PolicyId':\n // Policy ID would need to be fetched from API\n return undefined;\n default:\n return physicalId;\n }\n }\n\n // IAM User\n if (resourceType === 'AWS::IAM::User') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:user/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // IAM Group\n if (resourceType === 'AWS::IAM::Group') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:group/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // IAM InstanceProfile\n if (resourceType === 'AWS::IAM::InstanceProfile') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:instance-profile/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // KMS Key\n if (resourceType === 'AWS::KMS::Key') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:kms:${region}:${accountId}:key/${physicalId}`;\n case 'KeyId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // Cognito UserPool\n if (resourceType === 'AWS::Cognito::UserPool') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:cognito-idp:${region}:${accountId}:userpool/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // Kinesis Stream\n if (resourceType === 'AWS::Kinesis::Stream') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:kinesis:${region}:${accountId}:stream/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // EventBridge Rule. Custom event bus ARN: rule/{busName}/{ruleName};\n // default bus ARN: rule/{ruleName}. By the time constructAttribute runs,\n // properties.EventBusName (if templated) has been resolved to a literal\n // string or ARN by the deploy engine. Treat 'default' / unset as default bus.\n if (resourceType === 'AWS::Events::Rule') {\n switch (attributeName) {\n case 'Arn': {\n const busRaw = resource.properties?.['EventBusName'];\n const bus = typeof busRaw === 'string' && busRaw && busRaw !== 'default' ? busRaw : '';\n // If EventBusName resolved to an ARN, extract the bus name segment\n const busName = bus.startsWith('arn:') ? bus.split('/').pop() || '' : bus;\n return busName\n ? `arn:${partition}:events:${region}:${accountId}:rule/${busName}/${physicalId}`\n : `arn:${partition}:events:${region}:${accountId}:rule/${physicalId}`;\n }\n default:\n return physicalId;\n }\n }\n\n // EventBridge EventBus\n if (resourceType === 'AWS::Events::EventBus') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:events:${region}:${accountId}:event-bus/${physicalId}`;\n case 'Name':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // EFS FileSystem\n if (resourceType === 'AWS::EFS::FileSystem') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:elasticfilesystem:${region}:${accountId}:file-system/${physicalId}`;\n case 'FileSystemId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // Kinesis Data Firehose DeliveryStream\n if (resourceType === 'AWS::KinesisFirehose::DeliveryStream') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:firehose:${region}:${accountId}:deliverystream/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // CodeBuild Project\n if (resourceType === 'AWS::CodeBuild::Project') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:codebuild:${region}:${accountId}:project/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // CloudTrail Trail\n if (resourceType === 'AWS::CloudTrail::Trail') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:cloudtrail:${region}:${accountId}:trail/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // AppSync GraphQLApi (physicalId is the apiId)\n if (resourceType === 'AWS::AppSync::GraphQLApi') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:appsync:${region}:${accountId}:apis/${physicalId}`;\n case 'ApiId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // ServiceDiscovery PrivateDnsNamespace (physicalId is the namespace id)\n if (resourceType === 'AWS::ServiceDiscovery::PrivateDnsNamespace') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:servicediscovery:${region}:${accountId}:namespace/${physicalId}`;\n case 'Id':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // ServiceDiscovery Service (physicalId is the service id)\n if (resourceType === 'AWS::ServiceDiscovery::Service') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:servicediscovery:${region}:${accountId}:service/${physicalId}`;\n case 'Id':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // CloudWatch Alarm (note: 'alarm:' separator, not '/')\n if (resourceType === 'AWS::CloudWatch::Alarm') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:cloudwatch:${region}:${accountId}:alarm:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // RDS DBInstance (DocDB and Neptune share the same rds: service prefix and db: separator)\n if (\n resourceType === 'AWS::RDS::DBInstance' ||\n resourceType === 'AWS::DocDB::DBInstance' ||\n resourceType === 'AWS::Neptune::DBInstance'\n ) {\n switch (attributeName) {\n case 'DBInstanceArn':\n case 'Arn':\n return `arn:${partition}:rds:${region}:${accountId}:db:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // RDS DBCluster (DocDB and Neptune share the same rds: service prefix and cluster: separator)\n if (\n resourceType === 'AWS::RDS::DBCluster' ||\n resourceType === 'AWS::DocDB::DBCluster' ||\n resourceType === 'AWS::Neptune::DBCluster'\n ) {\n switch (attributeName) {\n case 'DBClusterArn':\n case 'Arn':\n return `arn:${partition}:rds:${region}:${accountId}:cluster:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // S3 Express Directory Bucket\n if (resourceType === 'AWS::S3Express::DirectoryBucket') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:s3express:${region}:${accountId}:bucket/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // Lambda Function\n if (resourceType === 'AWS::Lambda::Function') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:lambda:${region}:${accountId}:function:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // SQS Queue\n if (resourceType === 'AWS::SQS::Queue') {\n // Physical ID for SQS Queue is the queue URL\n // Extract queue name from URL: https://sqs.region.amazonaws.com/accountId/queueName\n let queueName = physicalId;\n if (physicalId.startsWith('https://')) {\n const parts = physicalId.split('/');\n queueName = parts[parts.length - 1] || physicalId;\n }\n\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:sqs:${region}:${accountId}:${queueName}`;\n case 'QueueUrl':\n return physicalId; // Physical ID is already the queue URL\n case 'QueueName':\n return queueName;\n default:\n return physicalId;\n }\n }\n\n // SNS Topic\n if (resourceType === 'AWS::SNS::Topic') {\n switch (attributeName) {\n case 'TopicArn':\n return `arn:${partition}:sns:${region}:${accountId}:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // CloudWatch Logs Log Group\n if (resourceType === 'AWS::Logs::LogGroup') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:logs:${region}:${accountId}:log-group:${physicalId}:*`;\n default:\n return physicalId;\n }\n }\n\n // ECR Repository\n if (resourceType === 'AWS::ECR::Repository') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:ecr:${region}:${accountId}:repository/${physicalId}`;\n case 'RepositoryUri':\n return `${accountId}.dkr.ecr.${region}.amazonaws.com/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // ECS Cluster\n if (resourceType === 'AWS::ECS::Cluster') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:ecs:${region}:${accountId}:cluster/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // EC2 Security Group\n if (resourceType === 'AWS::EC2::SecurityGroup') {\n switch (attributeName) {\n case 'GroupId':\n return physicalId; // Physical ID is already the group ID (sg-xxx)\n case 'VpcId':\n return undefined; // Would need API call\n default:\n return physicalId;\n }\n }\n\n // EC2 Subnet\n if (resourceType === 'AWS::EC2::Subnet') {\n switch (attributeName) {\n case 'SubnetId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // EC2 LaunchTemplate — `LatestVersionNumber` / `DefaultVersionNumber`\n // are AWS-derived integers that cdkd does not capture in state.\n // Resolve via `DescribeLaunchTemplates`. Return as a string so\n // downstream consumers (`AWS::AutoScaling::AutoScalingGroup`'s\n // `LaunchTemplate.Version`) get the form AWS accepts. Falling back\n // to the physical ID — as the previous default did — produced\n // `Invalid launch template version: either '$Default', '$Latest',\n // or a numeric version are allowed.` on `CreateAutoScalingGroup`.\n if (resourceType === 'AWS::EC2::LaunchTemplate') {\n if (attributeName === 'LatestVersionNumber' || attributeName === 'DefaultVersionNumber') {\n try {\n const clients = getAwsClients();\n const response = await clients.ec2.send(\n new DescribeLaunchTemplatesCommand({ LaunchTemplateIds: [physicalId] })\n );\n const lt = response.LaunchTemplates?.[0];\n const value =\n attributeName === 'LatestVersionNumber'\n ? lt?.LatestVersionNumber\n : lt?.DefaultVersionNumber;\n if (value !== undefined && value !== null) {\n return String(value);\n }\n } catch (err) {\n this.logger.warn(\n `DescribeLaunchTemplates(${physicalId}) failed for ${attributeName}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n // Fallback to \"$Latest\" / \"$Default\" — both are AWS-accepted\n // strings for the corresponding semantic, and let AWS pick the\n // version at API call time. Better than the resource-id\n // physicalId fallback which AWS rejects.\n return attributeName === 'LatestVersionNumber' ? '$Latest' : '$Default';\n }\n return physicalId;\n }\n\n // Default: return physical ID\n this.logger.warn(\n `Unknown attribute ${attributeName} for resource type ${resourceType}, returning physical ID`\n );\n return physicalId;\n }\n\n /**\n * Resolve Fn::Join intrinsic function\n *\n * Fn::Join: [delimiter, [value1, value2, ...]]\n */\n private async resolveJoin(\n joinArgs: [string, unknown[]],\n context: ResolverContext\n ): Promise<string> {\n const [delimiter, values] = joinArgs;\n\n // Resolve each value first\n const resolvedValues = await Promise.all(\n values.map(async (v) => {\n const resolved = await this.resolveValue(v, context);\n return String(resolved);\n })\n );\n\n let result = resolvedValues.join(delimiter);\n // Resolve any dynamic references in the joined result\n if (result.includes('{{resolve:')) {\n result = await this.resolveDynamicReferences(result);\n }\n this.logger.debug(`Resolved Fn::Join: ${result}`);\n return result;\n }\n\n /**\n * Resolve Fn::Sub intrinsic function\n *\n * Fn::Sub supports two forms:\n * 1. String with ${VarName} placeholders\n * 2. [String, {VarName: value, ...}] with explicit variable mapping\n *\n * Note: This is a simplified implementation that doesn't handle async properly\n * inside replace(). For full async support, we'd need to collect all replacements\n * first, then do them synchronously.\n */\n private async resolveSub(\n subArgs: string | [string, Record<string, unknown>],\n context: ResolverContext\n ): Promise<string> {\n let template: string;\n let variables: Record<string, unknown> = {};\n\n if (Array.isArray(subArgs)) {\n [template, variables] = subArgs;\n // Resolve variable values\n for (const [key, val] of Object.entries(variables)) {\n variables[key] = await this.resolveValue(val, context);\n }\n } else {\n template = subArgs;\n }\n\n // Collect all replacements\n const replacements: Array<{ match: string; replacement: string }> = [];\n const matches = template.matchAll(/\\$\\{([^}]+)\\}/g);\n\n for (const match of matches) {\n const varNameStr = match[1];\n if (!varNameStr) {\n continue; // Skip if no capture group\n }\n\n let replacement: string;\n\n // Check explicit variables first\n if (varNameStr in variables) {\n replacement = String(variables[varNameStr]);\n } else {\n // Check if it's a pseudo parameter\n const pseudoValue = await this.resolvePseudoParameter(varNameStr, context);\n if (pseudoValue !== undefined) {\n replacement = String(pseudoValue);\n } else {\n // Try to resolve as Ref\n try {\n const value = await this.resolveRef(varNameStr, context);\n replacement = String(value);\n } catch {\n // If not found, try to resolve as GetAtt (e.g., \"Resource.Attribute\")\n if (varNameStr.includes('.')) {\n try {\n const value = await this.resolveGetAtt(varNameStr, context);\n replacement = String(value);\n } catch {\n this.logger.warn(`Fn::Sub variable ${varNameStr} not found, keeping placeholder`);\n replacement = match[0]; // Keep original placeholder\n }\n } else {\n this.logger.warn(`Fn::Sub variable ${varNameStr} not found, keeping placeholder`);\n replacement = match[0]; // Keep original placeholder\n }\n }\n }\n }\n\n replacements.push({ match: match[0], replacement });\n }\n\n // Apply all replacements\n let result = template;\n for (const { match, replacement } of replacements) {\n result = result.replace(match, replacement);\n }\n\n // Resolve any dynamic references in the substituted result\n if (result.includes('{{resolve:')) {\n result = await this.resolveDynamicReferences(result);\n }\n this.logger.debug(`Resolved Fn::Sub: ${result}`);\n return result;\n }\n\n /**\n * Resolve Fn::Select intrinsic function\n *\n * Fn::Select: [index, [value1, value2, ...]]\n * Returns the value at the specified index in the list\n */\n private async resolveSelect(\n selectArgs: [number, unknown[]],\n context: ResolverContext\n ): Promise<unknown> {\n const [index, list] = selectArgs;\n\n // Resolve the list first\n const resolvedList = await this.resolveValue(list, context);\n\n if (!Array.isArray(resolvedList)) {\n throw new Error(`Fn::Select: list must be an array, got ${typeof resolvedList}`);\n }\n\n if (index < 0 || index >= resolvedList.length) {\n this.logger.warn(\n `Fn::Select: index ${index} out of bounds (array length: ${resolvedList.length})`\n );\n return `{{Fn::Select:${index}:OutOfBounds}}`;\n }\n\n const result: unknown = resolvedList[index];\n this.logger.debug(`Resolved Fn::Select: index ${index} -> ${JSON.stringify(result)}`);\n return result;\n }\n\n /**\n * Resolve Fn::Split intrinsic function\n *\n * Fn::Split: [delimiter, string]\n * Splits a string into a list of strings using the specified delimiter\n */\n private async resolveSplit(\n splitArgs: [string, unknown],\n context: ResolverContext\n ): Promise<string[]> {\n const [delimiter, value] = splitArgs;\n\n // Resolve the value first\n const resolvedValue = await this.resolveValue(value, context);\n\n if (typeof resolvedValue !== 'string') {\n throw new Error(`Fn::Split: value must be a string, got ${typeof resolvedValue}`);\n }\n\n const result = resolvedValue.split(delimiter);\n this.logger.debug(`Resolved Fn::Split: split by \"${delimiter}\" -> ${JSON.stringify(result)}`);\n return result;\n }\n\n /**\n * Resolve Fn::If intrinsic function\n *\n * Fn::If: [conditionName, valueIfTrue, valueIfFalse]\n * Returns valueIfTrue if condition evaluates to true, otherwise valueIfFalse\n */\n private async resolveIf(\n ifArgs: [string, unknown, unknown],\n context: ResolverContext\n ): Promise<unknown> {\n const [conditionName, valueIfTrue, valueIfFalse] = ifArgs;\n\n // Check if condition is evaluated in context\n if (!context.conditions || !(conditionName in context.conditions)) {\n this.logger.warn(`Condition ${conditionName} not found in context, assuming false`);\n return await this.resolveValue(valueIfFalse, context);\n }\n\n const conditionValue = context.conditions[conditionName];\n const selectedValue = conditionValue ? valueIfTrue : valueIfFalse;\n\n this.logger.debug(\n `Resolved Fn::If: condition ${conditionName} = ${conditionValue}, selected ${conditionValue ? 'true' : 'false'} branch`\n );\n\n return await this.resolveValue(selectedValue, context);\n }\n\n /**\n * Resolve Fn::Equals intrinsic function\n *\n * Fn::Equals: [value1, value2]\n * Returns true if both values are equal after resolution\n */\n private async resolveEquals(\n equalsArgs: [unknown, unknown],\n context: ResolverContext\n ): Promise<boolean> {\n const [value1, value2] = equalsArgs;\n\n // Resolve both values\n const resolved1 = await this.resolveValue(value1, context);\n const resolved2 = await this.resolveValue(value2, context);\n\n // Deep equality check\n const result = JSON.stringify(resolved1) === JSON.stringify(resolved2);\n\n this.logger.debug(\n `Resolved Fn::Equals: ${JSON.stringify(resolved1)} === ${JSON.stringify(resolved2)} -> ${result}`\n );\n\n return result;\n }\n\n /**\n * Resolve Fn::And intrinsic function\n *\n * Returns true if all conditions evaluate to true\n * Syntax: { \"Fn::And\": [ condition1, condition2, ... ] }\n */\n private async resolveAnd(conditions: unknown[], context: ResolverContext): Promise<boolean> {\n if (!Array.isArray(conditions) || conditions.length < 2 || conditions.length > 10) {\n throw new Error(`Fn::And requires between 2 and 10 conditions, got ${conditions.length}`);\n }\n\n // Resolve all conditions\n const results: boolean[] = [];\n for (const condition of conditions) {\n const resolved = await this.resolveValue(condition, context);\n results.push(Boolean(resolved));\n }\n\n // Return true if all are true\n const result = results.every((r) => r === true);\n\n this.logger.debug(`Resolved Fn::And: [${results.join(', ')}] -> ${result}`);\n\n return result;\n }\n\n /**\n * Resolve Fn::Or intrinsic function\n *\n * Returns true if at least one condition evaluates to true\n * Syntax: { \"Fn::Or\": [ condition1, condition2, ... ] }\n */\n private async resolveOr(conditions: unknown[], context: ResolverContext): Promise<boolean> {\n if (!Array.isArray(conditions) || conditions.length < 2 || conditions.length > 10) {\n throw new Error(`Fn::Or requires between 2 and 10 conditions, got ${conditions.length}`);\n }\n\n // Resolve all conditions\n const results: boolean[] = [];\n for (const condition of conditions) {\n const resolved = await this.resolveValue(condition, context);\n results.push(Boolean(resolved));\n }\n\n // Return true if at least one is true\n const result = results.some((r) => r === true);\n\n this.logger.debug(`Resolved Fn::Or: [${results.join(', ')}] -> ${result}`);\n\n return result;\n }\n\n /**\n * Resolve Fn::Not intrinsic function\n *\n * Returns the inverse of the condition\n * Syntax: { \"Fn::Not\": [ condition ] }\n */\n private async resolveNot(notArgs: [unknown], context: ResolverContext): Promise<boolean> {\n if (!Array.isArray(notArgs) || notArgs.length !== 1) {\n throw new Error(\n `Fn::Not requires exactly one condition, got ${Array.isArray(notArgs) ? notArgs.length : 0}`\n );\n }\n\n const [condition] = notArgs;\n\n // Resolve the condition\n const resolved = await this.resolveValue(condition, context);\n const result = !resolved;\n\n this.logger.debug(`Resolved Fn::Not: ${Boolean(resolved)} -> ${result}`);\n\n return result;\n }\n\n /**\n * Resolve Fn::ImportValue (cross-stack references)\n *\n * Searches all other stacks for an exported output with the given name.\n */\n private async resolveImportValue(\n importValueArg: unknown,\n context: ResolverContext\n ): Promise<unknown> {\n // First, resolve the export name (it might contain intrinsic functions)\n const exportName = await this.resolveValue(importValueArg, context);\n\n if (typeof exportName !== 'string') {\n throw new Error(\n `Fn::ImportValue: export name must resolve to a string, got ${typeof exportName}`\n );\n }\n\n // Check if we have a state backend\n if (!context.stateBackend) {\n throw new Error('Fn::ImportValue: state backend is required for cross-stack references');\n }\n\n this.logger.debug(`Resolving Fn::ImportValue: ${exportName}`);\n\n // Hot path: consult the persistent exports index for O(1) lookup.\n // Skip self-references (a stack importing its own export) so the\n // fallback scan below can apply the same exclusion.\n if (context.exportIndex) {\n try {\n const entry = await context.exportIndex.lookup(exportName);\n if (entry && (!context.stackName || entry.producerStack !== context.stackName)) {\n this.recordImport(context, exportName, entry.producerStack, entry.producerRegion);\n this.logger.info(\n `Resolved Fn::ImportValue: ${exportName} = ${JSON.stringify(entry.value)} (from index: ${entry.producerStack} / ${entry.producerRegion})`\n );\n return entry.value;\n }\n } catch (err) {\n this.logger.warn(\n `Exports index lookup failed for '${exportName}': ${err instanceof Error ? err.message : String(err)}; falling back to state.json scan`\n );\n }\n }\n\n // Fallback path (index miss, drift, or no index supplied): scan every\n // stack's state.json. Same as the pre-index behavior.\n const allStacks = await context.stateBackend.listStacks();\n this.logger.debug(\n `Found ${allStacks.length} state record(s) to search for export: ${exportName}`\n );\n\n for (const ref of allStacks) {\n const { stackName: refStack, region: refRegion } = ref;\n if (context.stackName && refStack === context.stackName) {\n this.logger.debug(`Skipping current stack: ${refStack}`);\n continue;\n }\n\n try {\n const lookupRegion = refRegion ?? this.resolverRegion ?? '';\n if (!lookupRegion) {\n this.logger.debug(\n `No region available for stack '${refStack}' — skipping (cdkd cannot read state without a region)`\n );\n continue;\n }\n const stateData = await context.stateBackend.getState(refStack, lookupRegion);\n if (!stateData) {\n this.logger.debug(`No state found for stack: ${refStack} (${lookupRegion})`);\n continue;\n }\n\n const { state } = stateData;\n\n if (state.outputs && exportName in state.outputs) {\n const value = state.outputs[exportName];\n this.logger.info(\n `Resolved Fn::ImportValue: ${exportName} = ${JSON.stringify(value)} (from stack: ${refStack} / ${lookupRegion})`\n );\n // Patch the index with the just-discovered entry so subsequent\n // resolves hit the O(1) path. Best-effort — index write failures\n // are logged and don't fail the resolve.\n if (context.exportIndex) {\n context.exportIndex\n .patchEntry(exportName, {\n value,\n producerStack: refStack,\n producerRegion: lookupRegion,\n })\n .catch((err) => {\n this.logger.debug(\n `Failed to patch exports index for '${exportName}': ${err instanceof Error ? err.message : String(err)}`\n );\n });\n }\n this.recordImport(context, exportName, refStack, lookupRegion);\n return value;\n }\n } catch (error) {\n this.logger.warn(\n `Failed to read state for stack ${refStack}: ${error instanceof Error ? error.message : String(error)}`\n );\n continue;\n }\n }\n\n throw new Error(\n `Fn::ImportValue: export '${exportName}' not found in any stack. ` +\n `Searched ${allStacks.length} state record(s). ` +\n `Make sure the exporting stack has been deployed and the Output has an Export.Name property.`\n );\n }\n\n /**\n * Push a resolved `Fn::ImportValue` into the consumer's recorded-imports\n * bag (when supplied by the caller). Skips duplicates within the\n * SAME bag — multiple references to the same `(exportName,\n * sourceStack, sourceRegion)` triple emit one entry.\n *\n * Concurrency: the check + push pair is purely synchronous (no\n * `await` between `some()` and `push()`), so the JS event loop\n * cannot interleave a competing `recordImport` call between the\n * dedup check and the append. The bag's lifetime is per-deploy\n * (DeployEngine resets `this.recordedImports = []` at the top of\n * each `deploy()` call), so the bag identity already serves as\n * the dedup scope.\n *\n * Cross-context dedup: when callers share the same bag instance\n * across multiple ResolverContext objects (the typical pattern —\n * DeployEngine passes `this.recordedImports` into every resolver\n * context it constructs), the dedup naturally extends across\n * contexts because the `some()` reads the shared bag. Stashing\n * the dedup Set on `context.recordedImports` directly via a\n * property would break under `verbatimModuleSyntax`-style strict\n * typing; the array scan stays O(N) where N is the per-deploy\n * import count (typically < 20), which is fine.\n */\n private recordImport(\n context: ResolverContext,\n exportName: string,\n producerStack: string,\n producerRegion: string\n ): void {\n if (!context.recordedImports) return;\n const dup = context.recordedImports.some(\n (e) =>\n e.exportName === exportName &&\n e.sourceStack === producerStack &&\n e.sourceRegion === producerRegion\n );\n if (dup) return;\n context.recordedImports.push({\n exportName,\n sourceStack: producerStack,\n sourceRegion: producerRegion,\n });\n }\n\n /**\n * Resolve Fn::GetStackOutput (cross-stack / cross-region output reference)\n *\n * Shape: { \"Fn::GetStackOutput\": { \"StackName\": \"...\", \"OutputName\": \"...\",\n * \"Region\": \"...\", \"RoleArn\": \"...\" } }\n *\n * Unlike Fn::ImportValue, the producer stack is named explicitly and no\n * Export is required. cdkd reads the producer's `outputs` from the\n * region-scoped state record at\n * `s3://{bucket}/cdkd/{StackName}/{Region}/state.json`. When `Region` is\n * omitted, the consumer's deploy region is used.\n *\n * RoleArn (cross-account) is intentionally rejected — cdkd uses S3 state,\n * not CloudFormation DescribeStacks, so a cross-account reference would\n * require assuming the role and reading the producer's separate state\n * bucket. That path is not yet implemented; we surface a clear error\n * instead of silently downgrading.\n */\n private async resolveGetStackOutput(arg: unknown, context: ResolverContext): Promise<unknown> {\n if (!arg || typeof arg !== 'object' || Array.isArray(arg)) {\n throw new Error(\n `Fn::GetStackOutput: argument must be an object with StackName/OutputName/Region/RoleArn, got ${\n arg === null ? 'null' : Array.isArray(arg) ? 'array' : typeof arg\n }`\n );\n }\n const args = arg as Record<string, unknown>;\n\n if (!('StackName' in args)) {\n throw new Error('Fn::GetStackOutput: StackName is required');\n }\n if (!('OutputName' in args)) {\n throw new Error('Fn::GetStackOutput: OutputName is required');\n }\n\n const stackName = await this.resolveValue(args['StackName'], context);\n if (typeof stackName !== 'string' || stackName === '') {\n throw new Error(\n `Fn::GetStackOutput: StackName must resolve to a non-empty string, got ${typeof stackName}`\n );\n }\n\n const outputName = await this.resolveValue(args['OutputName'], context);\n if (typeof outputName !== 'string' || outputName === '') {\n throw new Error(\n `Fn::GetStackOutput: OutputName must resolve to a non-empty string, got ${typeof outputName}`\n );\n }\n\n let region = this.resolverRegion;\n if ('Region' in args && args['Region'] !== undefined && args['Region'] !== null) {\n const resolvedRegion = await this.resolveValue(args['Region'], context);\n if (typeof resolvedRegion !== 'string' || resolvedRegion === '') {\n throw new Error(\n `Fn::GetStackOutput: Region must resolve to a non-empty string, got ${typeof resolvedRegion}`\n );\n }\n region = resolvedRegion;\n }\n\n let roleArn: string | undefined;\n if ('RoleArn' in args && args['RoleArn'] !== undefined && args['RoleArn'] !== null) {\n const resolvedRoleArn = await this.resolveValue(args['RoleArn'], context);\n if (typeof resolvedRoleArn !== 'string' || resolvedRoleArn === '') {\n throw new Error(\n `Fn::GetStackOutput: RoleArn must resolve to a non-empty string, got ${typeof resolvedRoleArn}`\n );\n }\n roleArn = resolvedRoleArn;\n }\n\n if (roleArn) {\n throw new Error(\n `Fn::GetStackOutput: cross-account references via RoleArn are not yet supported by cdkd ` +\n `(StackName=${stackName}, Region=${region}, RoleArn=${roleArn}). ` +\n `cdkd reads outputs from S3 state instead of CloudFormation DescribeStacks, ` +\n `so cross-account requires assuming the role and reading the producer account's ` +\n `state bucket — not yet implemented.`\n );\n }\n\n if (!context.stateBackend) {\n throw new Error('Fn::GetStackOutput: state backend is required for cross-stack references');\n }\n\n // Reject obvious self-reference (same stack AND same region).\n if (context.stackName && context.stackName === stackName && region === this.resolverRegion) {\n throw new Error(\n `Fn::GetStackOutput: cannot reference own stack '${stackName}' in the same region '${region}'`\n );\n }\n\n this.logger.debug(\n `Resolving Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName}`\n );\n\n const stateData = await context.stateBackend.getState(stackName, region);\n if (!stateData) {\n throw new Error(\n `Fn::GetStackOutput: stack '${stackName}' not found in region '${region}'. ` +\n `Make sure the producer stack has been deployed via cdkd.`\n );\n }\n\n const outputs = stateData.state.outputs ?? {};\n if (!(outputName in outputs)) {\n const available = Object.keys(outputs).join(', ') || '(none)';\n throw new Error(\n `Fn::GetStackOutput: output '${outputName}' not found in stack '${stackName}' (${region}). ` +\n `Available outputs: ${available}`\n );\n }\n\n const value = outputs[outputName];\n this.logger.info(\n `Resolved Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName} -> ${JSON.stringify(\n value\n )}`\n );\n return value;\n }\n\n /**\n * Resolve Fn::FindInMap intrinsic function\n *\n * Fn::FindInMap: [MapName, TopLevelKey, SecondLevelKey]\n * Looks up a value in the Mappings section of the template\n */\n private async resolveFindInMap(\n findInMapArgs: [unknown, unknown, unknown],\n context: ResolverContext\n ): Promise<unknown> {\n const [rawMapName, rawTopLevelKey, rawSecondLevelKey] = findInMapArgs;\n\n // Recursively resolve each argument (they could be Refs or other intrinsic functions)\n const mapName = String(await this.resolveValue(rawMapName, context));\n const topLevelKey = String(await this.resolveValue(rawTopLevelKey, context));\n const secondLevelKey = String(await this.resolveValue(rawSecondLevelKey, context));\n\n // Access the Mappings section of the template\n const mappings = context.template.Mappings;\n if (!mappings) {\n throw new Error(`Fn::FindInMap: no Mappings section found in template`);\n }\n\n const map = mappings[mapName] as Record<string, Record<string, unknown>> | undefined;\n if (!map) {\n throw new Error(`Fn::FindInMap: mapping '${mapName}' not found in Mappings section`);\n }\n\n const topLevel = map[topLevelKey];\n if (!topLevel || typeof topLevel !== 'object') {\n throw new Error(\n `Fn::FindInMap: top-level key '${topLevelKey}' not found in mapping '${mapName}'`\n );\n }\n\n if (!(secondLevelKey in topLevel)) {\n throw new Error(\n `Fn::FindInMap: second-level key '${secondLevelKey}' not found in mapping '${mapName}' -> '${topLevelKey}'`\n );\n }\n\n const result = topLevel[secondLevelKey];\n this.logger.debug(\n `Resolved Fn::FindInMap: ${mapName}.${topLevelKey}.${secondLevelKey} -> ${JSON.stringify(result)}`\n );\n return result;\n }\n\n /**\n * Resolve Fn::Base64 intrinsic function\n *\n * Fn::Base64: valueToEncode\n * Returns the Base64 representation of the input string\n */\n private async resolveBase64(value: unknown, context: ResolverContext): Promise<string> {\n // Recursively resolve the value first (it could be another intrinsic function)\n const resolvedValue = await this.resolveValue(value, context);\n\n if (typeof resolvedValue !== 'string') {\n throw new Error(`Fn::Base64: value must resolve to a string, got ${typeof resolvedValue}`);\n }\n\n const result = Buffer.from(resolvedValue).toString('base64');\n this.logger.debug(`Resolved Fn::Base64: ${resolvedValue} -> ${result}`);\n return result;\n }\n\n /**\n * Resolve Fn::GetAZs intrinsic function\n *\n * Fn::GetAZs: region\n * Returns a list of availability zones for the specified region.\n * If region is empty string or {\"Ref\": \"AWS::Region\"}, uses the current region.\n * Results are cached per region to avoid repeated API calls.\n */\n private async resolveGetAZs(value: unknown, context: ResolverContext): Promise<string[]> {\n // Recursively resolve the value first (it could be a Ref or other intrinsic function)\n const resolvedValue = await this.resolveValue(value, context);\n\n let region: string;\n if (typeof resolvedValue === 'string' && resolvedValue !== '') {\n region = resolvedValue;\n } else {\n // Empty string or non-string: use current region\n const accountInfo = await getAccountInfo(this.resolverRegion);\n region = accountInfo.region;\n }\n\n // Check cache\n const cached = cachedAvailabilityZones[region];\n if (cached) {\n this.logger.debug(`Resolved Fn::GetAZs from cache: ${region} -> ${JSON.stringify(cached)}`);\n return cached;\n }\n\n // Call EC2 DescribeAvailabilityZones\n const awsClients = getAwsClients();\n const ec2Client = awsClients.ec2;\n\n try {\n const response = await ec2Client.send(\n new DescribeAvailabilityZonesCommand({\n Filters: [\n {\n Name: 'region-name',\n Values: [region],\n },\n {\n Name: 'state',\n Values: ['available'],\n },\n ],\n })\n );\n\n const azNames = (response.AvailabilityZones || [])\n .map((az) => az.ZoneName)\n .filter((name): name is string => name !== undefined)\n .sort();\n\n cachedAvailabilityZones[region] = azNames;\n this.logger.debug(`Resolved Fn::GetAZs: ${region} -> ${JSON.stringify(azNames)}`);\n return azNames;\n } catch (error) {\n throw new Error(\n `Fn::GetAZs: failed to describe availability zones for region '${region}': ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Resolve pseudo parameters\n *\n * Pseudo parameters are built-in CloudFormation references like AWS::Region\n */\n private async resolvePseudoParameter(\n name: string,\n context?: ResolverContext\n ): Promise<string | symbol | undefined> {\n switch (name) {\n case 'AWS::Region': {\n const accountInfo = await getAccountInfo(this.resolverRegion);\n return accountInfo.region;\n }\n\n case 'AWS::AccountId': {\n const accountInfo = await getAccountInfo(this.resolverRegion);\n return accountInfo.accountId;\n }\n\n case 'AWS::Partition': {\n const accountInfo = await getAccountInfo(this.resolverRegion);\n return accountInfo.partition;\n }\n\n case 'AWS::StackName':\n return context?.stackName ?? 'UnknownStack';\n\n case 'AWS::StackId': {\n // cdkd doesn't use CloudFormation stacks, generate a synthetic ID\n const info = await getAccountInfo(this.resolverRegion);\n return `arn:aws:cloudformation:${info.region}:${info.accountId}:stack/${context?.stackName ?? 'UnknownStack'}/cdkd`;\n }\n\n case 'AWS::URLSuffix':\n return 'amazonaws.com';\n\n case 'AWS::NotificationARNs':\n return undefined;\n\n case 'AWS::NoValue':\n // Return special symbol to indicate property should be omitted\n return AWS_NO_VALUE;\n\n default:\n return undefined;\n }\n }\n\n /**\n * Resolve CloudFormation Dynamic References in a string value\n *\n * Supports:\n * - {{resolve:secretsmanager:SECRET_ID:SecretString:JSON_KEY:VERSION_STAGE:VERSION_ID}}\n * - {{resolve:ssm:PARAMETER_NAME}}\n *\n * Results are cached to avoid repeated API calls.\n */\n async resolveDynamicReferences(value: string): Promise<string> {\n // Match all {{resolve:...}} patterns\n const pattern = /\\{\\{resolve:([^}]+)\\}\\}/g;\n let result = value;\n let match: RegExpExecArray | null;\n\n // Collect all matches first (to avoid issues with modifying string during iteration)\n const matches: Array<{ fullMatch: string; inner: string }> = [];\n while ((match = pattern.exec(value)) !== null) {\n matches.push({ fullMatch: match[0], inner: match[1]! });\n }\n\n for (const { fullMatch, inner } of matches) {\n // Check cache first\n if (fullMatch in cachedDynamicReferences) {\n result = result.replace(fullMatch, cachedDynamicReferences[fullMatch]!);\n continue;\n }\n\n const parts = inner.split(':');\n const service = parts[0];\n\n let resolved: string;\n\n if (service === 'secretsmanager') {\n resolved = await this.resolveSecretsManagerReference(inner);\n } else if (service === 'ssm') {\n resolved = await this.resolveSSMReference(parts);\n } else {\n this.logger.warn(`Unsupported dynamic reference service: ${service}`);\n continue;\n }\n\n cachedDynamicReferences[fullMatch] = resolved;\n result = result.replace(fullMatch, resolved);\n }\n\n return result;\n }\n\n /**\n * Resolve a Secrets Manager dynamic reference\n *\n * Format: secretsmanager:SECRET_ID:SecretString:JSON_KEY:VERSION_STAGE:VERSION_ID\n * SECRET_ID can be a simple name or an ARN (arn:aws:secretsmanager:REGION:ACCOUNT:secret:NAME)\n * which contains colons, so we cannot simply split on ':'.\n * Instead, we find ':SecretString:' or ':SecretBinary:' as the delimiter.\n */\n private async resolveSecretsManagerReference(inner: string): Promise<string> {\n // inner = \"secretsmanager:SECRET_ID:SecretString:JSON_KEY:VERSION_STAGE:VERSION_ID\"\n // Remove the \"secretsmanager:\" prefix\n const afterService = inner.substring('secretsmanager:'.length);\n\n // Find :SecretString: or :SecretBinary: as the delimiter between SECRET_ID and the rest\n let secretId: string;\n let jsonKey = '';\n let versionStage = '';\n let versionId = '';\n\n const secretStringIdx = afterService.indexOf(':SecretString:');\n const secretBinaryIdx = afterService.indexOf(':SecretBinary:');\n const delimiterIdx =\n secretStringIdx >= 0 && secretBinaryIdx >= 0\n ? Math.min(secretStringIdx, secretBinaryIdx)\n : secretStringIdx >= 0\n ? secretStringIdx\n : secretBinaryIdx;\n const delimiterLen =\n delimiterIdx >= 0 && delimiterIdx === secretBinaryIdx\n ? ':SecretBinary:'.length\n : ':SecretString:'.length;\n\n if (delimiterIdx >= 0) {\n secretId = afterService.substring(0, delimiterIdx);\n // remaining = \"JSON_KEY:VERSION_STAGE:VERSION_ID\"\n const remaining = afterService.substring(delimiterIdx + delimiterLen);\n const remainingParts = remaining.split(':');\n jsonKey = remainingParts[0] || '';\n versionStage = remainingParts[1] || '';\n versionId = remainingParts[2] || '';\n } else {\n // No :SecretString: or :SecretBinary: found, treat entire afterService as SECRET_ID\n secretId = afterService;\n }\n\n // Empty strings should be treated as undefined (handles trailing :: in references)\n if (!versionStage) {\n versionStage = 'AWSCURRENT';\n }\n\n if (!secretId) {\n throw new Error('Dynamic reference: secretsmanager SECRET_ID is required');\n }\n\n this.logger.debug(\n `Resolving dynamic reference: secretsmanager:${secretId}:SecretString:${jsonKey}:${versionStage}:${versionId}`\n );\n\n const awsClients = getAwsClients();\n const client = awsClients.secretsManager;\n\n const command = new GetSecretValueCommand({\n SecretId: secretId,\n ...(versionStage && versionStage !== '' && { VersionStage: versionStage }),\n ...(versionId && versionId !== '' && { VersionId: versionId }),\n });\n\n const response = await client.send(command);\n const secretString = response.SecretString;\n\n if (!secretString) {\n throw new Error(\n `Dynamic reference: secret '${secretId}' does not contain a SecretString value`\n );\n }\n\n // If JSON_KEY is specified, parse JSON and extract the key\n if (jsonKey) {\n try {\n const parsed = JSON.parse(secretString) as Record<string, unknown>;\n const keyValue = parsed[jsonKey];\n if (keyValue === undefined) {\n throw new Error(`Dynamic reference: key '${jsonKey}' not found in secret '${secretId}'`);\n }\n return stringifyValue(keyValue);\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(\n `Dynamic reference: secret '${secretId}' is not valid JSON but JSON_KEY '${jsonKey}' was specified`\n );\n }\n throw error;\n }\n }\n\n // No JSON_KEY: return full secret string\n return secretString;\n }\n\n /**\n * Resolve an SSM Parameter Store dynamic reference\n *\n * Format: ssm:PARAMETER_NAME\n * Parts[0] = 'ssm'\n * Parts[1] = PARAMETER_NAME\n */\n /**\n * Resolve Fn::Cidr intrinsic function\n *\n * Fn::Cidr returns an array of CIDR address blocks.\n * Syntax: { \"Fn::Cidr\": [ ipBlock, count, cidrBits ] }\n * - ipBlock: The user-specified CIDR address block to be split\n * - count: The number of CIDRs to generate\n * - cidrBits: The number of subnet bits for the CIDR (e.g., \"64\" for /64 in IPv6)\n */\n private async resolveCidr(\n args: [unknown, unknown, unknown],\n context: ResolverContext\n ): Promise<string[]> {\n const [rawIpBlock, rawCount, rawCidrBits] = args;\n const ipBlock = (await this.resolveValue(rawIpBlock, context)) as string;\n const count = Number(await this.resolveValue(rawCount, context));\n const cidrBits = Number(await this.resolveValue(rawCidrBits, context));\n\n if (!ipBlock || typeof ipBlock !== 'string') {\n throw new Error(\n `Fn::Cidr: ipBlock must be a string, got ${typeof ipBlock}: ${JSON.stringify(ipBlock)}`\n );\n }\n\n this.logger.debug(\n `Resolving Fn::Cidr: ipBlock=${ipBlock}, count=${count}, cidrBits=${cidrBits}`\n );\n\n const isIpv6 = ipBlock.includes(':');\n const results: string[] = [];\n\n if (isIpv6) {\n // IPv6 CIDR calculation\n // Parse the base IPv6 address and prefix\n const [baseAddr, prefixStr] = ipBlock.split('/');\n const basePrefix = parseInt(prefixStr!, 10);\n const subnetPrefix = 128 - cidrBits; // cidrBits = host bits, so subnet prefix = 128 - cidrBits\n\n // Expand IPv6 address to full form\n const expanded = this.expandIPv6(baseAddr!);\n const addrBigInt = this.ipv6ToBigInt(expanded);\n\n // Calculate subnet size\n const subnetSize = BigInt(1) << BigInt(128 - subnetPrefix);\n\n // Mask the base address to the network prefix\n const prefixMask =\n (BigInt(1) << BigInt(128)) -\n BigInt(1) -\n ((BigInt(1) << BigInt(128 - basePrefix)) - BigInt(1));\n const networkBase = addrBigInt & prefixMask;\n\n for (let i = 0; i < count; i++) {\n const subnetAddr = networkBase + subnetSize * BigInt(i);\n results.push(`${this.bigIntToIPv6(subnetAddr)}/${subnetPrefix}`);\n }\n } else {\n // IPv4 CIDR calculation\n const [baseAddr, prefixStr] = ipBlock.split('/');\n const basePrefix = parseInt(prefixStr!, 10);\n const subnetPrefix = 32 - cidrBits;\n\n const parts = baseAddr!.split('.').map(Number);\n const baseInt = ((parts[0]! << 24) | (parts[1]! << 16) | (parts[2]! << 8) | parts[3]!) >>> 0;\n const subnetSize = 1 << (32 - subnetPrefix);\n const prefixMask = (0xffffffff << (32 - basePrefix)) >>> 0;\n const networkBase = (baseInt & prefixMask) >>> 0;\n\n for (let i = 0; i < count; i++) {\n const subnetAddr = (networkBase + subnetSize * i) >>> 0;\n const a = (subnetAddr >>> 24) & 0xff;\n const b = (subnetAddr >>> 16) & 0xff;\n const c = (subnetAddr >>> 8) & 0xff;\n const d = subnetAddr & 0xff;\n results.push(`${a}.${b}.${c}.${d}/${subnetPrefix}`);\n }\n }\n\n this.logger.debug(`Fn::Cidr result: ${JSON.stringify(results)}`);\n return results;\n }\n\n /** Expand IPv6 address to full 8-group form */\n private expandIPv6(addr: string): string {\n // Handle :: expansion\n if (addr.includes('::')) {\n const [left, right] = addr.split('::');\n const leftParts = left ? left.split(':') : [];\n const rightParts = right ? right.split(':') : [];\n const missing = 8 - leftParts.length - rightParts.length;\n const middle = Array.from({ length: missing }, () => '0000');\n const all = [...leftParts, ...middle, ...rightParts];\n return all.map((p: string) => p.padStart(4, '0')).join(':');\n }\n return addr\n .split(':')\n .map((p) => p.padStart(4, '0'))\n .join(':');\n }\n\n /** Convert expanded IPv6 string to BigInt */\n private ipv6ToBigInt(expanded: string): bigint {\n const parts = expanded.split(':');\n let result = BigInt(0);\n for (const part of parts) {\n result = (result << BigInt(16)) | BigInt(parseInt(part, 16));\n }\n return result;\n }\n\n /** Convert BigInt to compressed IPv6 string */\n private bigIntToIPv6(n: bigint): string {\n const parts: string[] = [];\n for (let i = 7; i >= 0; i--) {\n parts.push(((n >> BigInt(i * 16)) & BigInt(0xffff)).toString(16));\n }\n // Simple format — don't compress with :: for clarity\n return parts.join(':');\n }\n\n private async resolveSSMReference(parts: string[]): Promise<string> {\n const parameterName = parts.slice(1).join(':');\n\n if (!parameterName) {\n throw new Error('Dynamic reference: ssm PARAMETER_NAME is required');\n }\n\n this.logger.debug(`Resolving dynamic reference: ssm:${parameterName}`);\n\n const awsClients = getAwsClients();\n const client = awsClients.ssm;\n\n const command = new GetParameterCommand({\n Name: parameterName,\n WithDecryption: true,\n });\n\n const response = await client.send(command);\n const paramValue = response.Parameter?.Value;\n\n if (paramValue === undefined || paramValue === null) {\n throw new Error(\n `Dynamic reference: SSM parameter '${parameterName}' not found or has no value`\n );\n }\n\n return paramValue;\n }\n}\n","/**\n * JSON Patch Generator for Cloud Control API\n *\n * Generates RFC 6902 compliant JSON Patch documents by comparing\n * previous and desired resource properties.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc6902\n */\n\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * JSON Patch operation types\n */\nexport type PatchOperation = 'add' | 'remove' | 'replace' | 'test';\n\n/**\n * JSON Patch operation\n */\nexport interface JsonPatchOp {\n op: PatchOperation;\n path: string;\n value?: unknown;\n}\n\n/**\n * JSON Patch Generator\n *\n * Creates minimal patch documents for Cloud Control API updates.\n */\nexport class JsonPatchGenerator {\n private logger = getLogger().child('JsonPatchGenerator');\n\n /**\n * Generate JSON Patch from property differences\n *\n * @param previousProperties - Previous resource properties\n * @param desiredProperties - Desired resource properties\n * @returns Array of JSON Patch operations\n */\n generatePatch(\n previousProperties: Record<string, unknown>,\n desiredProperties: Record<string, unknown>\n ): JsonPatchOp[] {\n const patches: JsonPatchOp[] = [];\n\n // Find added or changed properties\n for (const [key, value] of Object.entries(desiredProperties)) {\n const previousValue = previousProperties[key];\n\n if (previousValue === undefined) {\n // Property added\n patches.push({\n op: 'add',\n path: `/${this.escapeJsonPointer(key)}`,\n value,\n });\n } else if (!this.deepEqual(previousValue, value)) {\n // Property changed\n patches.push({\n op: 'replace',\n path: `/${this.escapeJsonPointer(key)}`,\n value,\n });\n }\n // else: no change, skip\n }\n\n // Find removed properties\n for (const key of Object.keys(previousProperties)) {\n if (!(key in desiredProperties)) {\n patches.push({\n op: 'remove',\n path: `/${this.escapeJsonPointer(key)}`,\n });\n }\n }\n\n this.logger.debug(`Generated ${patches.length} patch operations`);\n\n return patches;\n }\n\n /**\n * Generate a full replacement patch\n *\n * This is used as a fallback when property-level patching is not feasible.\n *\n * @param properties - Desired resource properties\n * @returns Single replace operation at root\n */\n generateFullReplacementPatch(properties: Record<string, unknown>): JsonPatchOp[] {\n return [\n {\n op: 'replace',\n path: '/',\n value: properties,\n },\n ];\n }\n\n /**\n * Escape JSON Pointer special characters\n *\n * Per RFC 6901, '~' and '/' must be escaped in JSON Pointer paths.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc6901\n */\n private escapeJsonPointer(str: string): string {\n return str.replace(/~/g, '~0').replace(/\\//g, '~1');\n }\n\n /**\n * Deep equality check for values\n *\n * Handles objects, arrays, primitives, null, and undefined.\n */\n private deepEqual(a: unknown, b: unknown): boolean {\n // Same reference or both null/undefined\n if (a === b) return true;\n\n // Different types\n if (typeof a !== typeof b) return false;\n\n // null comparison\n if (a === null || b === null) return false;\n\n // Array comparison\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, index) => this.deepEqual(item, b[index]));\n }\n\n // Object comparison\n if (typeof a === 'object' && typeof b === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n\n if (aKeys.length !== bKeys.length) return false;\n\n return aKeys.every((key) => this.deepEqual(aObj[key], bObj[key]));\n }\n\n // Primitive comparison (already handled by a === b above, but for clarity)\n return false;\n }\n}\n","import { ProvisioningError } from '../utils/error-handler.js';\n\n/**\n * Context passed to provider delete operations.\n *\n * `expectedRegion` is the region that the resource is expected to live in,\n * sourced from the stack state (`StackState.region`). When set, providers\n * use it to verify that a `NotFound` error from the AWS client is genuinely\n * \"the resource is gone\", and not a silent miss caused by the client\n * pointing at a different region than where the resource actually lives.\n */\nexport interface DeleteContext {\n /**\n * Region recorded in the stack state when the resource was created.\n * Optional: when omitted (or set to undefined), providers preserve their\n * existing idempotent behavior — i.e., NotFound is treated as success\n * without verification. The explicit `undefined` is permitted so callers\n * can spread `state.region` (which is itself `string | undefined`)\n * directly without first narrowing.\n */\n expectedRegion?: string | undefined;\n\n /**\n * If true, providers MUST flip per-resource deletion protection off\n * in-place before issuing the actual delete API call. Set by `cdkd\n * destroy --remove-protection` / `cdkd state destroy --remove-protection`.\n *\n * Providers handle the in-place flip-off only for protection-bearing\n * resource types (e.g. `AWS::Logs::LogGroup` `DeletionProtectionEnabled`,\n * `AWS::RDS::DBInstance` / `DBCluster` `DeletionProtection`,\n * `AWS::DocDB::DBCluster` `DeletionProtection` (DocDB DBInstance has\n * no protection field), `AWS::Neptune::DBCluster` /\n * `AWS::Neptune::DBInstance` `DeletionProtection`,\n * `AWS::DynamoDB::Table` `DeletionProtectionEnabled`,\n * `AWS::EC2::Instance` `DisableApiTermination`,\n * `AWS::ElasticLoadBalancingV2::LoadBalancer`\n * `deletion_protection.enabled` attribute). Resource types that do not\n * have a corresponding protection field treat this flag as a no-op —\n * the existing delete logic runs unchanged.\n *\n * The flip-off call is idempotent: it is always issued when this flag\n * is set (and protection is supported on the type), regardless of\n * whether the resource actually has protection enabled. AWS APIs\n * accept the no-op (already-disabled) case without error; \"not found\"\n * / similar errors during the flip-off are logged at debug and the\n * delete proceeds.\n *\n * When `false` (the default), providers behave exactly as before —\n * deletion protection blocks the destroy with whatever error AWS\n * returns (`OperationNotPermitted` / `InvalidParameterCombination` /\n * etc.) so the user must opt into the bypass explicitly.\n *\n * Note: prior to this flag, the RDS DBInstance / DBCluster providers\n * unconditionally issued a `ModifyDB{Instance,Cluster}` to clear\n * `DeletionProtection: false` before every destroy. That implicit\n * behavior is now gated on `removeProtection === true` to match the\n * other provider types — destroying an RDS resource whose deletion\n * protection was set externally (console, AWS CLI) without\n * `--remove-protection` will surface AWS's `InvalidParameterCombination`\n * error rather than silently succeed.\n */\n removeProtection?: boolean;\n}\n\n/**\n * Verify that the AWS client's region matches the region the resource is\n * expected to live in before treating a `NotFound` error as idempotent\n * delete success.\n *\n * Why: a destroy run with the wrong region would otherwise receive\n * `*NotFound` for every resource and silently strip them all from state,\n * leaving the actual AWS resources orphaned in the real region. The\n * silent-failure incident that motivated this check was a Lambda in\n * `us-west-2` removed from state by a destroy that ran with a `us-east-1`\n * client.\n *\n * Behavior:\n * - If `expectedRegion` is unset, this is a no-op (back-compat: existing\n * idempotent semantics preserved for callers that have not been\n * threaded with state region).\n * - If `clientRegion` matches `expectedRegion`, returns silently.\n * - Otherwise throws `ProvisioningError` so the caller surfaces the\n * mismatch instead of swallowing the NotFound.\n *\n * @param clientRegion Region resolved from the AWS SDK client config\n * (typically `await client.config.region()`).\n * @param expectedRegion Region recorded in stack state, or undefined if\n * the caller has no expected region.\n * @param resourceType CloudFormation resource type, used in the error\n * message and on the thrown ProvisioningError.\n * @param logicalId Logical ID of the resource, used in the error message\n * and on the thrown ProvisioningError.\n * @param physicalId Optional physical ID, used in the error message and\n * on the thrown ProvisioningError.\n */\nexport function assertRegionMatch(\n clientRegion: string | undefined,\n expectedRegion: string | undefined,\n resourceType: string,\n logicalId: string,\n physicalId?: string\n): void {\n if (!expectedRegion) {\n // Back-compat: caller did not supply state region, preserve previous\n // idempotent behavior.\n return;\n }\n\n if (!clientRegion) {\n throw new ProvisioningError(\n `Refusing to treat NotFound as idempotent delete success for ${logicalId} ` +\n `(${resourceType}): AWS client region is unknown but stack state expects ` +\n `${expectedRegion}. The resource may exist in ${expectedRegion} and would ` +\n `be silently removed from state if this NotFound were trusted.`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n if (clientRegion !== expectedRegion) {\n throw new ProvisioningError(\n `Refusing to treat NotFound as idempotent delete success for ${logicalId} ` +\n `(${resourceType}): AWS client region ${clientRegion} does not match stack ` +\n `state region ${expectedRegion}. The resource likely still exists in ` +\n `${expectedRegion}; rerun the destroy with the correct region (e.g. ` +\n `--region ${expectedRegion}).`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n}\n","import {\n CloudControlClient,\n CreateResourceCommand,\n UpdateResourceCommand,\n DeleteResourceCommand,\n GetResourceCommand,\n GetResourceRequestStatusCommand,\n type ProgressEvent,\n} from '@aws-sdk/client-cloudcontrol';\nimport { DescribeTableCommand } from '@aws-sdk/client-dynamodb';\nimport { GetRestApiCommand } from '@aws-sdk/client-api-gateway';\nimport { GetCloudFrontOriginAccessIdentityCommand } from '@aws-sdk/client-cloudfront';\nimport { GetFunctionUrlConfigCommand } from '@aws-sdk/client-lambda';\nimport { getAccountInfo } from '../deployment/intrinsic-function-resolver.js';\nimport { getAwsClients } from '../utils/aws-clients.js';\nimport { getLogger } from '../utils/logger.js';\nimport { ProvisioningError } from '../utils/error-handler.js';\nimport { JsonPatchGenerator } from './json-patch-generator.js';\nimport { assertRegionMatch, type DeleteContext } from './region-check.js';\nimport type {\n ResourceProvider,\n ResourceCreateResult,\n ResourceUpdateResult,\n ResourceImportInput,\n ResourceImportResult,\n} from '../types/resource.js';\n\n/**\n * AWS Cloud Control API Provider\n *\n * Provisions resources using the Cloud Control API, which provides\n * a unified interface for managing AWS resources.\n *\n * Note: Not all AWS resources are supported by Cloud Control API.\n * Use isSupportedResourceType() to check before usage.\n */\n/**\n * Properties that CC API expects as JSON strings, not objects.\n * CC API schema declares these as type: [\"string\", \"object\"] but\n * the implementation only accepts strings.\n */\nconst JSON_STRING_PROPERTIES: Record<string, Set<string>> = {\n 'AWS::Events::Rule': new Set(['EventPattern']),\n};\n\n/**\n * Stringify object properties that CC API expects as JSON strings.\n */\nfunction stringifyJsonProperties(\n resourceType: string,\n properties: Record<string, unknown>\n): Record<string, unknown> {\n const jsonProps = JSON_STRING_PROPERTIES[resourceType];\n if (!jsonProps) return properties;\n\n const result = { ...properties };\n for (const key of jsonProps) {\n if (key in result && typeof result[key] === 'object' && result[key] !== null) {\n result[key] = JSON.stringify(result[key]);\n }\n }\n return result;\n}\n\n/**\n * Recursively strip null and undefined values from an object.\n * This prevents CC API errors caused by null property values\n * (e.g., EventBridge Rule with null ScheduleExpression causes Java NPE).\n */\nfunction stripNullValues(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return undefined;\n }\n if (Array.isArray(obj)) {\n return obj.map(stripNullValues).filter((v) => v !== undefined);\n }\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n const stripped = stripNullValues(value);\n if (stripped !== undefined) {\n result[key] = stripped;\n }\n }\n return result;\n }\n return obj;\n}\n\nexport class CloudControlProvider implements ResourceProvider {\n private cloudControlClient: CloudControlClient;\n private logger = getLogger().child('CloudControlProvider');\n private patchGenerator = new JsonPatchGenerator();\n\n // Maximum time to wait for operation completion (15 minutes)\n private readonly MAX_WAIT_TIME_MS = 15 * 60 * 1000;\n // Initial poll interval (1 second) - increases with 1.5x exponential backoff\n private readonly INITIAL_POLL_INTERVAL_MS = 1_000;\n // Maximum poll interval (10 seconds)\n private readonly MAX_POLL_INTERVAL_MS = 10_000;\n\n constructor() {\n const awsClients = getAwsClients();\n this.cloudControlClient = awsClients.cloudControl;\n }\n\n /**\n * Create a resource using Cloud Control API\n */\n async create(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n ): Promise<ResourceCreateResult> {\n this.logger.debug(`Creating resource ${logicalId} (${resourceType})`);\n\n try {\n // Start resource creation\n const cleanProperties = stripNullValues(properties) as Record<string, unknown>;\n const ccProperties = stringifyJsonProperties(resourceType, cleanProperties);\n const desiredState = JSON.stringify(ccProperties);\n this.logger.debug(`DesiredState for ${logicalId}: ${desiredState}`);\n const createResponse = await this.cloudControlClient.send(\n new CreateResourceCommand({\n TypeName: resourceType,\n DesiredState: desiredState,\n })\n );\n\n if (!createResponse.ProgressEvent?.RequestToken) {\n throw new ProvisioningError(\n `Failed to create resource ${logicalId}: No request token received`,\n resourceType,\n logicalId\n );\n }\n\n this.logger.debug(\n `Create request submitted for ${logicalId}, token: ${createResponse.ProgressEvent.RequestToken}`\n );\n\n // Wait for creation to complete\n const progressEvent = await this.waitForOperation(\n createResponse.ProgressEvent.RequestToken,\n logicalId,\n 'CREATE'\n );\n\n if (!progressEvent.Identifier) {\n throw new ProvisioningError(\n `Failed to create resource ${logicalId}: No physical ID returned`,\n resourceType,\n logicalId\n );\n }\n\n this.logger.debug(`Created resource ${logicalId}, physical ID: ${progressEvent.Identifier}`);\n\n // Parse resource properties to extract attributes\n const result: ResourceCreateResult = {\n physicalId: progressEvent.Identifier,\n };\n\n if (progressEvent.ResourceModel) {\n result.attributes = this.parseResourceModel(progressEvent.ResourceModel);\n }\n\n // Enrich attributes with computed values for specific resource types\n result.attributes = await this.enrichResourceAttributes(\n resourceType,\n progressEvent.Identifier,\n result.attributes || {}\n );\n\n return result;\n } catch (error) {\n this.handleError(error, 'CREATE', resourceType, logicalId);\n }\n }\n\n /**\n * Update a resource using Cloud Control API\n */\n async update(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n previousProperties: Record<string, unknown>\n ): Promise<ResourceUpdateResult> {\n this.logger.debug(\n `Updating resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`\n );\n\n try {\n // Strip null/undefined values and stringify JSON properties before generating patch\n const cleanPreviousProperties = stringifyJsonProperties(\n resourceType,\n stripNullValues(previousProperties) as Record<string, unknown>\n );\n const cleanProperties = stringifyJsonProperties(\n resourceType,\n stripNullValues(properties) as Record<string, unknown>\n );\n\n // Generate JSON Patch document\n const patch = this.patchGenerator.generatePatch(cleanPreviousProperties, cleanProperties);\n\n if (patch.length === 0) {\n // No changes detected\n this.logger.debug(`No property changes detected for ${logicalId}, skipping update`);\n return {\n physicalId,\n wasReplaced: false,\n };\n }\n\n this.logger.debug(\n `Generated ${patch.length} patch operations for ${logicalId}: ${JSON.stringify(patch)}`\n );\n\n // Start resource update\n const updateResponse = await this.cloudControlClient.send(\n new UpdateResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n PatchDocument: JSON.stringify(patch),\n })\n );\n\n if (!updateResponse.ProgressEvent?.RequestToken) {\n throw new ProvisioningError(\n `Failed to update resource ${logicalId}: No request token received`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n this.logger.debug(\n `Update request submitted for ${logicalId}, token: ${updateResponse.ProgressEvent.RequestToken}`\n );\n\n // Wait for update to complete\n const progressEvent = await this.waitForOperation(\n updateResponse.ProgressEvent.RequestToken,\n logicalId,\n 'UPDATE'\n );\n\n this.logger.debug(`Updated resource ${logicalId}`);\n\n // Parse resource properties to extract attributes\n // Resource replacement for immutable property changes is detected and handled\n // by DeployEngine (immutable property detection + CREATE→DELETE flow) before\n // reaching this update method, so wasReplaced is always false here.\n const result: ResourceUpdateResult = {\n physicalId,\n wasReplaced: false,\n };\n\n if (progressEvent.ResourceModel) {\n result.attributes = this.parseResourceModel(progressEvent.ResourceModel);\n }\n\n // Enrich attributes with computed values for specific resource types\n result.attributes = await this.enrichResourceAttributes(\n resourceType,\n physicalId,\n result.attributes || {}\n );\n\n return result;\n } catch (error) {\n this.handleError(error, 'UPDATE', resourceType, logicalId, physicalId);\n }\n }\n\n /**\n * Delete a resource using Cloud Control API\n */\n async delete(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n _properties?: Record<string, unknown>,\n context?: DeleteContext\n ): Promise<void> {\n this.logger.debug(\n `Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`\n );\n\n try {\n // Start resource deletion\n const deleteResponse = await this.cloudControlClient.send(\n new DeleteResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n })\n );\n\n if (!deleteResponse.ProgressEvent?.RequestToken) {\n throw new ProvisioningError(\n `Failed to delete resource ${logicalId}: No request token received`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n this.logger.debug(\n `Delete request submitted for ${logicalId}, token: ${deleteResponse.ProgressEvent.RequestToken}`\n );\n\n // Wait for deletion to complete\n await this.waitForOperation(deleteResponse.ProgressEvent.RequestToken, logicalId, 'DELETE');\n\n this.logger.debug(`Deleted resource ${logicalId}`);\n } catch (error) {\n // Treat \"not found\" / \"does not exist\" as idempotent success for DELETE,\n // but only when the AWS client is operating against the same region the\n // resource was deployed to. A region mismatch must surface — otherwise a\n // destroy run with the wrong region would silently strip every resource\n // from state while leaving the actual AWS resources orphaned.\n const err = error as { name?: string; message?: string };\n if (\n err.name === 'ResourceNotFoundException' ||\n err.message?.includes('does not exist') ||\n err.message?.includes('not found') ||\n err.message?.includes('NotFound')\n ) {\n const clientRegion = await this.cloudControlClient.config.region();\n assertRegionMatch(\n clientRegion,\n context?.expectedRegion,\n resourceType,\n logicalId,\n physicalId\n );\n this.logger.debug(`Resource ${logicalId} already deleted (not found), treating as success`);\n return;\n }\n this.handleError(error, 'DELETE', resourceType, logicalId, physicalId);\n }\n }\n\n /**\n * Get current state of a resource\n */\n async getResourceState(\n resourceType: string,\n physicalId: string\n ): Promise<Record<string, unknown> | null> {\n try {\n const response = await this.cloudControlClient.send(\n new GetResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n })\n );\n\n if (!response.ResourceDescription?.Properties) {\n return null;\n }\n\n return this.parseResourceModel(response.ResourceDescription.Properties);\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Wait for an asynchronous operation to complete\n */\n private async waitForOperation(\n requestToken: string,\n logicalId: string,\n operation: 'CREATE' | 'UPDATE' | 'DELETE'\n ): Promise<ProgressEvent> {\n const startTime = Date.now();\n let attempts = 0;\n let pollInterval = this.INITIAL_POLL_INTERVAL_MS;\n\n while (Date.now() - startTime < this.MAX_WAIT_TIME_MS) {\n attempts++;\n\n const statusResponse = await this.cloudControlClient.send(\n new GetResourceRequestStatusCommand({\n RequestToken: requestToken,\n })\n );\n\n const progressEvent = statusResponse.ProgressEvent;\n\n if (!progressEvent) {\n throw new ProvisioningError(\n `Failed to get status for ${logicalId}: No progress event`,\n 'Unknown',\n logicalId\n );\n }\n\n this.logger.debug(\n `${operation} ${logicalId}: ${progressEvent.OperationStatus} (attempt ${attempts}, next poll ${pollInterval}ms)`\n );\n\n switch (progressEvent.OperationStatus) {\n case 'SUCCESS':\n return progressEvent;\n\n case 'FAILED':\n throw new ProvisioningError(\n `${operation} failed for ${logicalId}: ${progressEvent.StatusMessage || 'Unknown error'}`,\n progressEvent.TypeName || 'Unknown',\n logicalId,\n progressEvent.Identifier\n );\n\n case 'CANCEL_COMPLETE':\n throw new ProvisioningError(\n `${operation} cancelled for ${logicalId}`,\n progressEvent.TypeName || 'Unknown',\n logicalId,\n progressEvent.Identifier\n );\n\n case 'IN_PROGRESS':\n case 'PENDING':\n // Exponential backoff with 1.5x multiplier for flatter curve:\n // 1s → 1.5s → 2.25s → 3.4s → 5s → 7.5s → 10s (capped)\n // Most CC API operations complete in 1-5s, so slower ramp-up\n // polls more frequently during the common case.\n await this.sleep(pollInterval);\n pollInterval = Math.min(Math.ceil(pollInterval * 1.5), this.MAX_POLL_INTERVAL_MS);\n break;\n\n default:\n this.logger.warn(\n `Unknown operation status for ${logicalId}: ${progressEvent.OperationStatus}`\n );\n await this.sleep(pollInterval);\n pollInterval = Math.min(Math.ceil(pollInterval * 1.5), this.MAX_POLL_INTERVAL_MS);\n }\n }\n\n throw new ProvisioningError(\n `${operation} timeout for ${logicalId} after ${this.MAX_WAIT_TIME_MS / 1000}s`,\n 'Unknown',\n logicalId\n );\n }\n\n /**\n * Parse resource model JSON string\n */\n private parseResourceModel(resourceModel: string): Record<string, unknown> {\n try {\n return JSON.parse(resourceModel) as Record<string, unknown>;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.warn(\n `Failed to parse resource model: ${errorMessage}\\n` +\n `Raw model: ${resourceModel.substring(0, 500)}${resourceModel.length > 500 ? '...' : ''}`\n );\n return {};\n }\n }\n\n /**\n * Enrich resource attributes with computed values\n *\n * CC API GetResource returns property names that match CloudFormation\n * Fn::GetAtt attribute names, so all properties are passed through as-is.\n * This method adds fallback attributes for edge cases where CC API\n * may not return certain values.\n */\n private async enrichResourceAttributes(\n resourceType: string,\n physicalId: string,\n attributes: Record<string, unknown>\n ): Promise<Record<string, unknown>> {\n const enriched: Record<string, unknown> = { ...attributes };\n\n // Fallback: compute attributes that CC API may not return\n switch (resourceType) {\n case 'AWS::S3::Bucket':\n // S3 bucket ARN: arn:aws:s3:::bucket-name\n if (!enriched['Arn']) {\n enriched['Arn'] = `arn:aws:s3:::${physicalId}`;\n }\n break;\n\n case 'AWS::DynamoDB::Table':\n // Fallback: CC API GetResource may not include StreamArn when streams are enabled.\n // Call DescribeTable to retrieve LatestStreamArn if not already present.\n if (!enriched['StreamArn']) {\n try {\n const dynamoDBClient = getAwsClients().dynamoDB;\n const describeResponse = await dynamoDBClient.send(\n new DescribeTableCommand({ TableName: physicalId })\n );\n const latestStreamArn = describeResponse.Table?.LatestStreamArn;\n if (latestStreamArn) {\n enriched['StreamArn'] = latestStreamArn;\n this.logger.debug(\n `Enriched DynamoDB StreamArn for ${physicalId}: ${latestStreamArn}`\n );\n }\n } catch (error) {\n // Best-effort: don't fail the operation if DescribeTable fails\n this.logger.debug(\n `Failed to get DynamoDB StreamArn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n case 'AWS::ApiGateway::RestApi':\n // Fallback: ensure RootResourceId is present.\n // CC API GetResource typically returns it, but retrieve via SDK if missing.\n if (!enriched['RootResourceId']) {\n try {\n const apiGatewayClient = getAwsClients().apiGateway;\n const getRestApiResponse = await apiGatewayClient.send(\n new GetRestApiCommand({ restApiId: physicalId })\n );\n if (getRestApiResponse.rootResourceId) {\n enriched['RootResourceId'] = getRestApiResponse.rootResourceId;\n this.logger.debug(\n `Enriched RestApi RootResourceId for ${physicalId}: ${getRestApiResponse.rootResourceId}`\n );\n }\n } catch (error) {\n // Best-effort: don't fail the operation if GetRestApi fails\n this.logger.debug(\n `Failed to get RestApi RootResourceId for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n // Ensure RestApiId is set (physical ID is the rest-api-id)\n if (!enriched['RestApiId']) {\n enriched['RestApiId'] = physicalId;\n }\n break;\n\n case 'AWS::CloudFront::CloudFrontOriginAccessIdentity':\n // Fallback: ensure S3CanonicalUserId is present.\n // CC API GetResource typically returns it, but retrieve via SDK if missing.\n if (!enriched['S3CanonicalUserId']) {\n try {\n const cloudFrontClient = getAwsClients().cloudFront;\n const oaiResponse = await cloudFrontClient.send(\n new GetCloudFrontOriginAccessIdentityCommand({ Id: physicalId })\n );\n const s3CanonicalUserId = oaiResponse.CloudFrontOriginAccessIdentity?.S3CanonicalUserId;\n if (s3CanonicalUserId) {\n enriched['S3CanonicalUserId'] = s3CanonicalUserId;\n this.logger.debug(\n `Enriched CloudFront OAI S3CanonicalUserId for ${physicalId}: ${s3CanonicalUserId}`\n );\n }\n } catch (error) {\n // Best-effort: don't fail the operation\n this.logger.debug(\n `Failed to get CloudFront OAI S3CanonicalUserId for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n case 'AWS::KMS::Key':\n // CC API may not return Arn in ResourceModel.\n // Physical ID is the KeyId (UUID), so construct the ARN.\n if (!enriched['Arn']) {\n try {\n const kmsAccountInfo = await getAccountInfo();\n enriched['Arn'] =\n `arn:${kmsAccountInfo.partition}:kms:${kmsAccountInfo.region}:${kmsAccountInfo.accountId}:key/${physicalId}`;\n this.logger.debug(`Enriched KMS Key Arn for ${physicalId}: ${String(enriched['Arn'])}`);\n } catch (error) {\n this.logger.debug(\n `Failed to construct KMS Key Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n if (!enriched['KeyId']) {\n enriched['KeyId'] = physicalId;\n }\n break;\n\n case 'AWS::CloudFront::OriginAccessControl':\n // CC API physicalId is the OAC ID\n if (!enriched['Id']) enriched['Id'] = physicalId;\n break;\n\n case 'AWS::Route53::HealthCheck':\n // CC API physicalId is the HealthCheck ID\n if (!enriched['HealthCheckId']) enriched['HealthCheckId'] = physicalId;\n break;\n\n case 'AWS::ECR::Repository':\n // CC API physicalId is the repository name, construct ARN\n if (!enriched['Arn']) {\n try {\n const ecrAccountInfo = await getAccountInfo();\n enriched['Arn'] =\n `arn:${ecrAccountInfo.partition}:ecr:${ecrAccountInfo.region}:${ecrAccountInfo.accountId}:repository/${physicalId}`;\n this.logger.debug(\n `Enriched ECR Repository Arn for ${physicalId}: ${String(enriched['Arn'])}`\n );\n } catch (error) {\n this.logger.debug(\n `Failed to construct ECR Repository Arn: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n if (!enriched['RepositoryUri']) {\n try {\n const ecrAccountInfo = await getAccountInfo();\n enriched['RepositoryUri'] =\n `${ecrAccountInfo.accountId}.dkr.ecr.${ecrAccountInfo.region}.amazonaws.com/${physicalId}`;\n } catch {\n /* best effort */\n }\n }\n break;\n\n case 'AWS::EC2::EIP':\n // CC API returns composite physicalId: \"PublicIp|AllocationId\"\n // Extract individual attributes for Fn::GetAtt resolution\n if (physicalId.includes('|')) {\n const [publicIp, allocationId] = physicalId.split('|');\n if (!enriched['AllocationId']) enriched['AllocationId'] = allocationId;\n if (!enriched['PublicIp']) enriched['PublicIp'] = publicIp;\n this.logger.debug(\n `Enriched EIP attributes: AllocationId=${allocationId}, PublicIp=${publicIp}`\n );\n }\n break;\n\n case 'AWS::Lambda::Version':\n // CC API physicalId for Lambda Version is the full version ARN\n // (e.g., arn:aws:lambda:us-east-1:123456:function:MyFunc:1).\n // Lambda::Alias FunctionVersion property needs just the version number.\n if (!enriched['Version']) {\n const versionSegments = physicalId.split(':');\n const versionNumber = versionSegments[versionSegments.length - 1];\n enriched['Version'] = versionNumber;\n this.logger.debug(`Enriched Lambda Version for ${physicalId}: ${versionNumber}`);\n }\n break;\n\n case 'AWS::Kinesis::Stream':\n // CC API physicalId for Kinesis Stream is the stream name, not the ARN.\n // Fn::GetAtt [Stream, Arn] needs the full ARN.\n if (!enriched['Arn']) {\n try {\n const kinesisAccountInfo = await getAccountInfo();\n enriched['Arn'] =\n `arn:${kinesisAccountInfo.partition}:kinesis:${kinesisAccountInfo.region}:${kinesisAccountInfo.accountId}:stream/${physicalId}`;\n this.logger.debug(\n `Enriched Kinesis Stream Arn for ${physicalId}: ${String(enriched['Arn'])}`\n );\n } catch (error) {\n this.logger.debug(\n `Failed to construct Kinesis Stream Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n case 'AWS::Lambda::Url':\n // CC API CREATE response may not include FunctionUrl in ResourceModel.\n // Use Lambda SDK to retrieve it for Fn::GetAtt resolution.\n if (!enriched['FunctionUrl']) {\n try {\n const lambdaClient = getAwsClients().lambda;\n // physicalId is the FunctionArn for Lambda URL\n const urlConfig = await lambdaClient.send(\n new GetFunctionUrlConfigCommand({ FunctionName: physicalId })\n );\n if (urlConfig.FunctionUrl) {\n enriched['FunctionUrl'] = urlConfig.FunctionUrl;\n this.logger.debug(\n `Enriched Lambda URL FunctionUrl for ${physicalId}: ${urlConfig.FunctionUrl}`\n );\n }\n if (urlConfig.FunctionArn) {\n enriched['FunctionArn'] = urlConfig.FunctionArn;\n }\n } catch (error) {\n this.logger.debug(\n `Failed to get Lambda URL config for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n default:\n break;\n }\n\n return enriched;\n }\n\n /**\n * Handle errors and throw ProvisioningError\n */\n private handleError(\n error: unknown,\n operation: string,\n resourceType: string,\n logicalId: string,\n physicalId?: string\n ): never {\n const err = error as { name?: string; message?: string };\n\n // Check if resource type is not supported\n if (err.name === 'UnsupportedActionException' || err.name === 'TypeNotFoundException') {\n throw new ProvisioningError(\n `Resource type ${resourceType} is not supported by Cloud Control API and no SDK provider is registered.\\n` +\n `Please report this issue at https://github.com/go-to-k/cdkd/issues so we can add SDK provider support.\\n` +\n `Error: ${err.message || 'Unknown error'}`,\n resourceType,\n logicalId,\n physicalId,\n error instanceof Error ? error : undefined\n );\n }\n\n // Re-throw if already a ProvisioningError\n if (error instanceof ProvisioningError) {\n throw error;\n }\n\n // Wrap other errors\n throw new ProvisioningError(\n `${operation} failed for ${logicalId}: ${err.message || 'Unknown error'}`,\n resourceType,\n logicalId,\n physicalId,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * Sleep for specified milliseconds\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Check if a resource type is supported by Cloud Control API\n *\n * This is a best-effort check. Some resource types may still fail\n * even if they appear to be supported.\n */\n static isSupportedResourceType(resourceType: string): boolean {\n // Common resource types that are NOT supported by Cloud Control API\n const unsupportedTypes = new Set([\n // IAM (most types not supported)\n 'AWS::IAM::Role',\n 'AWS::IAM::Policy',\n 'AWS::IAM::ManagedPolicy',\n 'AWS::IAM::User',\n 'AWS::IAM::Group',\n 'AWS::IAM::InstanceProfile',\n\n // Lambda layers\n 'AWS::Lambda::LayerVersion',\n\n // S3 bucket policies (use SDK instead)\n 'AWS::S3::BucketPolicy',\n\n // CloudFormation-specific resources\n 'AWS::CloudFormation::Stack',\n 'AWS::CloudFormation::WaitCondition',\n 'AWS::CloudFormation::WaitConditionHandle',\n 'AWS::CloudFormation::CustomResource',\n\n // CDK-specific resources\n 'AWS::CDK::Metadata',\n 'Custom::CDKBucketDeployment',\n 'Custom::S3AutoDeleteObjects',\n\n // Route53 hosted zones (complex)\n 'AWS::Route53::HostedZone',\n\n // ACM certificates (validation complexity)\n 'AWS::CertificateManager::Certificate',\n ]);\n\n if (unsupportedTypes.has(resourceType)) {\n return false;\n }\n\n // Custom resources are never supported by Cloud Control\n if (\n resourceType.startsWith('Custom::') ||\n resourceType.startsWith('AWS::CloudFormation::CustomResource')\n ) {\n return false;\n }\n\n // Most other AWS:: resources should be supported\n // (This is optimistic; some may still fail)\n return resourceType.startsWith('AWS::');\n }\n\n /**\n * Read the AWS-current properties of a resource managed via Cloud Control\n * API, for `cdkd drift` comparison.\n *\n * Strategy: `GetResource(TypeName, Identifier)` returns `ResourceModel` as\n * a JSON string of every property AWS reports for the resource. Parse and\n * surface it as the AWS-current snapshot — the drift command intersects\n * this against the keys present in cdkd state, so AWS-only keys (timestamps,\n * generated ids, etc.) are filtered out at compare time.\n *\n * Returns `undefined` for the unique cases that mean \"drift unknown\" (the\n * resource was deleted out from under cdkd, or the response had no\n * Properties field). Re-throws on any other error so the drift command can\n * surface throttling / access-denied issues to the user.\n *\n * This single CC API implementation gives drift detection coverage to every\n * resource type that goes through CC API — the majority of cdkd's surface.\n * SDK Providers add their own `readCurrentState` incrementally (PR D).\n */\n async readCurrentState(\n physicalId: string,\n _logicalId: string,\n resourceType: string,\n _properties?: Record<string, unknown>\n ): Promise<Record<string, unknown> | undefined> {\n try {\n const response = await this.cloudControlClient.send(\n new GetResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n })\n );\n\n const raw = response.ResourceDescription?.Properties;\n if (typeof raw !== 'string' || raw.length === 0) {\n return undefined;\n }\n\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return undefined;\n }\n\n return parsed as Record<string, unknown>;\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * Adopt an already-deployed resource into cdkd state via Cloud Control API.\n *\n * Strategy: explicit-override only.\n * - With `knownPhysicalId` (from `--resource <id>=<physicalId>` or\n * `--resource-mapping`): call `GetResource(TypeName, Identifier)`,\n * parse `ResourceModel` (returned as a JSON string by CC API), and\n * return its keys as `attributes`.\n * - Without `knownPhysicalId`: return `null`. CC API has no efficient\n * `aws:cdk:path`-tag lookup — `ListResources` returns identifiers\n * only, so tag lookup would require one `GetResource` per resource\n * in the account, plus per-service tag-API calls (which CC API\n * doesn't expose uniformly). Cost vs. value isn't worth it; users\n * who need adoption for CC-API-only resource types should pass\n * `--resource <id>=<physicalId>` for those resources.\n *\n * SDK providers (S3, Lambda, IAM Role, etc.) implement their own\n * `import` with tag-based auto-lookup; this fallback only kicks in for\n * resource types that don't have a dedicated SDK provider.\n */\n async import(input: ResourceImportInput): Promise<ResourceImportResult | null> {\n if (!input.knownPhysicalId) {\n // Explicit-override-only: no auto lookup via CC API.\n return null;\n }\n\n try {\n const resp = await this.cloudControlClient.send(\n new GetResourceCommand({\n TypeName: input.resourceType,\n Identifier: input.knownPhysicalId,\n })\n );\n\n // CC API returns `ResourceModel` as a JSON string of all the\n // resource's properties — its keys map 1:1 to GetAtt-compatible\n // attribute names. Parse and surface them so deploy-time\n // `Fn::GetAtt` resolution can find them in state.\n let attributes: Record<string, unknown> = {};\n const raw = resp.ResourceDescription?.Properties;\n if (typeof raw === 'string' && raw.length > 0) {\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n attributes = parsed as Record<string, unknown>;\n }\n } catch (parseErr) {\n this.logger.debug(\n `Failed to parse CC API ResourceModel for ${input.resourceType}/${input.knownPhysicalId}: ${\n parseErr instanceof Error ? parseErr.message : String(parseErr)\n }`\n );\n // Fall through with empty attributes — physicalId is enough\n // to register the resource in state. Fn::GetAtt will\n // reconstruct attributes via constructAttribute at deploy.\n }\n }\n\n return { physicalId: input.knownPhysicalId, attributes };\n } catch (error) {\n // ResourceNotFoundException → null (caller marks \"not found\").\n // Any other error (access denied, bad TypeName, throttling) →\n // re-throw so the caller can surface it.\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return null;\n }\n throw error;\n }\n }\n}\n","import {\n LambdaClient,\n InvokeCommand,\n waitUntilFunctionActiveV2,\n waitUntilFunctionUpdatedV2,\n type InvocationResponse,\n} from '@aws-sdk/client-lambda';\nimport { SNSClient, PublishCommand } from '@aws-sdk/client-sns';\nimport {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { getLogger } from '../../utils/logger.js';\nimport { getAwsClients } from '../../utils/aws-clients.js';\nimport { ProvisioningError } from '../../utils/error-handler.js';\nimport { type DeleteContext } from '../region-check.js';\nimport type {\n ResourceProvider,\n ResourceCreateResult,\n ResourceUpdateResult,\n ResourceImportInput,\n ResourceImportResult,\n} from '../../types/resource.js';\n\n/**\n * CloudFormation Custom Resource Response format\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html\n */\ninterface CfnCustomResourceResponse {\n Status: 'SUCCESS' | 'FAILED';\n Reason?: string;\n PhysicalResourceId?: string;\n StackId?: string;\n RequestId?: string;\n LogicalResourceId?: string;\n NoEcho?: boolean;\n Data?: Record<string, unknown>;\n}\n\n/**\n * Custom Resource Lambda Response Payload (direct return)\n * Some handlers return data directly in the Lambda payload instead of via ResponseURL\n */\ninterface CustomResourceResponsePayload {\n PhysicalResourceId?: string;\n Data?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Configuration for Custom Resource Provider\n */\nexport interface CustomResourceProviderConfig {\n /** S3 bucket name for storing custom resource responses */\n responseBucket?: string;\n /** S3 key prefix for response objects */\n responsePrefix?: string;\n /**\n * Max time (ms) to wait for async custom resource responses (e.g., CDK Provider framework\n * with isCompleteHandler that uses Step Functions polling).\n * Default: 1 hour (3600000ms), matching CDK's default totalTimeout.\n */\n asyncResponseTimeoutMs?: number;\n}\n\n/**\n * Type guard to validate Lambda response payload structure\n */\nfunction isCustomResourceResponsePayload(value: unknown): value is CustomResourceResponsePayload {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n\n const payload = value as Record<string, unknown>;\n\n if ('PhysicalResourceId' in payload && typeof payload['PhysicalResourceId'] !== 'string') {\n return false;\n }\n\n if ('Data' in payload) {\n if (typeof payload['Data'] !== 'object' || payload['Data'] === null) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Parse Lambda response payload with type safety\n */\nfunction parseLambdaPayload(payloadBytes: Uint8Array | undefined): CustomResourceResponsePayload {\n if (!payloadBytes) {\n return {};\n }\n\n const payloadString = Buffer.from(payloadBytes).toString();\n\n // Handle empty or null responses\n if (!payloadString || payloadString === 'null' || payloadString === '\"\"') {\n return {};\n }\n\n const parsed: unknown = JSON.parse(payloadString);\n\n if (!isCustomResourceResponsePayload(parsed)) {\n throw new Error(`Invalid Lambda response payload format: ${JSON.stringify(parsed)}`);\n }\n\n return parsed;\n}\n\n/**\n * Custom Resource Provider\n *\n * Implements Lambda-backed custom resources by invoking the Lambda function\n * specified in the ServiceToken property.\n *\n * This provider follows the CloudFormation custom resource protocol:\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/custom-resources.html\n *\n * Supports both standard custom resources and CDK's Provider framework:\n *\n * **Standard custom resources:**\n * - ServiceToken Lambda is invoked synchronously\n * - Handler sends cfn-response to ResponseURL (S3 pre-signed URL) or returns directly\n * - Short polling timeout (30 seconds)\n *\n * **CDK Provider framework (with isCompleteHandler):**\n * - ServiceToken points to the framework's onEvent wrapper Lambda\n * - Lambda invokes user's onEventHandler, then starts a Step Functions state machine\n * - Step Functions polls the isCompleteHandler until IsComplete: true\n * - Step Functions sends cfn-response to ResponseURL when done\n * - Lambda returns null/empty payload (async pattern detected automatically)\n * - Long polling timeout with exponential backoff (default: 1 hour)\n *\n * Response handling strategy:\n * 1. Generate a pre-signed S3 PUT URL as the ResponseURL (valid for 2 hours)\n * 2. Invoke Lambda synchronously (RequestResponse)\n * 3. Check Lambda payload for direct response (simple handlers)\n * 4. If no direct response, detect async pattern and poll S3 with appropriate timeout\n */\nexport class CustomResourceProvider implements ResourceProvider {\n private lambdaClient: LambdaClient;\n private snsClient: SNSClient;\n private s3Client: S3Client;\n private logger = getLogger().child('CustomResourceProvider');\n private responseBucket: string | undefined;\n private responsePrefix: string;\n\n /**\n * Opt out of the deploy engine's outer transient-error retry loop.\n *\n * The loop re-invokes `provider.create()` from the top on a transient\n * SDK error (IAM propagation, HTTP 429/503, etc.). Each invocation\n * generates a brand-new RequestId and a brand-new pre-signed S3\n * response URL via `prepareInvocation()`. If the underlying Lambda has\n * already started — e.g. an outer retry fired between the placeholder\n * `PutObject` and the `Invoke`, or after the `Invoke` returned but a\n * spurious downstream error fired — the first attempt's Lambda\n * response lands at an S3 key that nobody polls, hanging the deploy\n * until the polling timeout. The provider already polls with its own\n * exponential backoff for async patterns (CDK Provider framework with\n * isCompleteHandler), so an outer retry adds nothing but the multi-\n * key bug.\n */\n readonly disableOuterRetry = true;\n\n /** Max time to wait for synchronous S3 response after Lambda invocation (30 seconds) */\n private readonly SYNC_RESPONSE_TIMEOUT_MS = 30_000;\n /** Max time to wait for async S3 response (CDK Provider framework with isCompleteHandler) */\n private readonly asyncResponseTimeoutMs: number;\n /** Default async response timeout: 1 hour (matches CDK's default totalTimeout) */\n private static readonly DEFAULT_ASYNC_RESPONSE_TIMEOUT_MS = 3_600_000;\n /** Initial poll interval for checking S3 response (2 seconds) */\n private readonly INITIAL_POLL_INTERVAL_MS = 2_000;\n /** Max poll interval for async polling with exponential backoff (30 seconds) */\n private readonly MAX_POLL_INTERVAL_MS = 30_000;\n\n constructor(config?: CustomResourceProviderConfig) {\n const awsClients = getAwsClients();\n this.lambdaClient = awsClients.lambda;\n this.snsClient = awsClients.sns;\n this.s3Client = awsClients.s3;\n this.responseBucket = config?.responseBucket;\n this.responsePrefix = config?.responsePrefix ?? 'custom-resource-responses';\n this.asyncResponseTimeoutMs =\n config?.asyncResponseTimeoutMs ?? CustomResourceProvider.DEFAULT_ASYNC_RESPONSE_TIMEOUT_MS;\n }\n\n /**\n * Self-reported minimum per-resource timeout.\n *\n * Custom Resource async invocations (CDK Provider framework with\n * `isCompleteHandler`) poll for up to `asyncResponseTimeoutMs`\n * (default 1 hour, matching CDK's `totalTimeout` default). The deploy\n * engine's global `--resource-timeout` default is 30 minutes, which\n * would abort a perfectly healthy CR mid-poll. By self-reporting the\n * polling cap, the engine lifts the deadline to `max(self-report,\n * global)` for CR resources only; a user-supplied per-type override\n * (`--resource-timeout AWS::CloudFormation::CustomResource=5m`) still\n * wins for explicit escape-hatching.\n */\n getMinResourceTimeoutMs(): number {\n return this.asyncResponseTimeoutMs;\n }\n\n /**\n * Set the S3 bucket for custom resource responses\n * Called by ProviderRegistry when state bucket is configured\n */\n setResponseBucket(bucket: string, bucketRegion?: string): void {\n this.responseBucket = bucket;\n // For cross-region deploy: S3 client for response bucket must use the bucket's region,\n // not the stack's region. The state bucket is always in the base region.\n if (bucketRegion) {\n this.s3Client = new S3Client(bucketRegion ? { region: bucketRegion } : {});\n }\n }\n\n /**\n * Create a custom resource by invoking its Lambda handler\n */\n async create(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n ): Promise<ResourceCreateResult> {\n this.logger.debug(`Creating custom resource ${logicalId} (${resourceType})`);\n\n const serviceToken = properties['ServiceToken'];\n\n if (!serviceToken) {\n throw new ProvisioningError(\n `ServiceToken is required for custom resource ${logicalId}`,\n resourceType,\n logicalId\n );\n }\n\n if (typeof serviceToken !== 'string') {\n throw new ProvisioningError(\n `Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). ` +\n `This usually indicates state was written by a pre-fix cdkd import; ` +\n `re-run \\`cdkd import\\` or \\`cdkd state orphan <stack>\\` to recover.`,\n resourceType,\n logicalId\n );\n }\n\n try {\n const invocation = await this.prepareInvocation();\n\n const request = {\n RequestType: 'Create',\n RequestId: invocation.requestId,\n ResponseURL: invocation.responseURL,\n ResourceType: resourceType,\n LogicalResourceId: logicalId,\n StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,\n ResourceProperties: this.stringifyProperties(properties),\n };\n\n this.logger.debug(`Sending custom resource create request: ${serviceToken}`);\n\n const cfnResponse = await this.sendRequest(\n serviceToken,\n request,\n invocation.responseKey,\n logicalId,\n 'Create'\n );\n\n if (cfnResponse.Status === 'FAILED') {\n throw new Error(\n `Custom resource handler returned FAILED: ${cfnResponse.Reason || 'Unknown reason'}`\n );\n }\n\n const physicalId: string = cfnResponse.PhysicalResourceId || logicalId;\n const attributes: Record<string, unknown> = cfnResponse.Data || {};\n\n this.logger.debug(`Successfully created custom resource ${logicalId}: ${physicalId}`);\n\n return { physicalId, attributes };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to create custom resource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n undefined,\n cause\n );\n }\n }\n\n /**\n * Update a custom resource by invoking its Lambda handler\n */\n async update(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n previousProperties: Record<string, unknown>\n ): Promise<ResourceUpdateResult> {\n this.logger.debug(`Updating custom resource ${logicalId}: ${physicalId} (${resourceType})`);\n\n const serviceToken = properties['ServiceToken'];\n\n if (!serviceToken) {\n throw new ProvisioningError(\n `ServiceToken is required for custom resource ${logicalId}`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n if (typeof serviceToken !== 'string') {\n throw new ProvisioningError(\n `Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). ` +\n `This usually indicates state was written by a pre-fix cdkd import; ` +\n `re-run \\`cdkd import\\` or \\`cdkd state orphan <stack>\\` to recover.`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n try {\n const invocation = await this.prepareInvocation();\n\n const request = {\n RequestType: 'Update',\n RequestId: invocation.requestId,\n ResponseURL: invocation.responseURL,\n ResourceType: resourceType,\n LogicalResourceId: logicalId,\n PhysicalResourceId: physicalId,\n StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,\n ResourceProperties: this.stringifyProperties(properties),\n OldResourceProperties: this.stringifyProperties(previousProperties),\n };\n\n this.logger.debug(`Sending custom resource update request: ${serviceToken}`);\n\n const cfnResponse = await this.sendRequest(\n serviceToken,\n request,\n invocation.responseKey,\n logicalId,\n 'Update'\n );\n\n if (cfnResponse.Status === 'FAILED') {\n throw new Error(\n `Custom resource handler returned FAILED: ${cfnResponse.Reason || 'Unknown reason'}`\n );\n }\n\n const newPhysicalId: string = cfnResponse.PhysicalResourceId || physicalId;\n const wasReplaced: boolean = newPhysicalId !== physicalId;\n const attributes: Record<string, unknown> = cfnResponse.Data || {};\n\n this.logger.debug(\n `Successfully updated custom resource ${logicalId}: ${newPhysicalId}${wasReplaced ? ' (replaced)' : ''}`\n );\n\n return { physicalId: newPhysicalId, wasReplaced, attributes };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to update custom resource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n /**\n * Delete a custom resource by invoking its Lambda handler\n */\n async delete(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties?: Record<string, unknown>,\n _context?: DeleteContext\n ): Promise<void> {\n // Custom resources delegate deletion to a user-provided Lambda handler.\n // The Lambda invocation itself does not surface a `*NotFound` for the\n // managed resource, so the region-mismatch check has no signal to act on\n // here; the underlying Lambda's region is determined by its ARN, which is\n // already encoded in the ServiceToken regardless of the cdkd client's\n // region. The context parameter is accepted for interface conformity.\n this.logger.debug(`Deleting custom resource ${logicalId}: ${physicalId} (${resourceType})`);\n\n if (!properties) {\n this.logger.warn(\n `No properties available for custom resource ${logicalId}, skipping deletion`\n );\n return;\n }\n\n const serviceToken = properties['ServiceToken'];\n\n if (!serviceToken) {\n this.logger.warn(`No ServiceToken found for custom resource ${logicalId}, skipping deletion`);\n return;\n }\n\n if (typeof serviceToken !== 'string') {\n throw new ProvisioningError(\n `Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). ` +\n `This usually indicates state was written by a pre-fix cdkd import; ` +\n `re-run \\`cdkd import\\` or \\`cdkd state orphan <stack>\\` to recover.`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n try {\n const invocation = await this.prepareInvocation();\n\n const request = {\n RequestType: 'Delete',\n RequestId: invocation.requestId,\n ResponseURL: invocation.responseURL,\n ResourceType: resourceType,\n LogicalResourceId: logicalId,\n PhysicalResourceId: physicalId,\n StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,\n ResourceProperties: this.stringifyProperties(properties),\n };\n\n this.logger.debug(`Sending custom resource delete request: ${serviceToken}`);\n\n const cfnResponse = await this.sendRequest(\n serviceToken,\n request,\n invocation.responseKey,\n logicalId,\n 'Delete'\n );\n\n if (cfnResponse.Status === 'FAILED') {\n this.logger.warn(\n `Custom resource delete handler returned FAILED for ${logicalId}: ${cfnResponse.Reason || 'Unknown reason'}`\n );\n } else {\n this.logger.debug(`Successfully deleted custom resource ${logicalId}`);\n }\n } catch (error) {\n // For deletion, we should be more lenient with errors\n this.logger.warn(\n `Failed to delete custom resource ${logicalId}, but continuing: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Check if a ServiceToken is an SNS topic ARN\n */\n isSnsServiceToken(serviceToken: string): boolean {\n return serviceToken.startsWith('arn:aws:sns:');\n }\n\n /**\n * Send custom resource request via the appropriate service (Lambda or SNS)\n * For Lambda: invokes synchronously and returns the response\n * For SNS: publishes to topic and polls S3 for response\n */\n private async sendRequest(\n serviceToken: string,\n request: Record<string, unknown>,\n responseKey: string,\n logicalId: string,\n operation: string\n ): Promise<CfnCustomResourceResponse> {\n if (this.isSnsServiceToken(serviceToken)) {\n this.logger.debug(`ServiceToken is SNS topic, publishing to: ${serviceToken}`);\n await this.publishToSns(serviceToken, request);\n return await this.pollS3Response(responseKey, logicalId, operation);\n }\n\n // Block until the backing Lambda is in a ready-to-Invoke state. The\n // Lambda CREATE / UPDATE returns synchronously while State / LastUpdateStatus\n // is still `Pending` / `InProgress`; a synchronous Invoke against\n // either fails with \"The function is currently in the following\n // state: Pending\" / \"InProgress\" (see PR #121). We wait HERE — at the\n // one consumer that breaks against not-ready Lambdas — instead of\n // gating every Lambda CREATE on Active, which doubled deploy time on\n // VPC-Lambda benchmark stacks.\n await this.waitForBackingLambdaReady(serviceToken, logicalId);\n\n const response = await this.invokeLambda(serviceToken, request);\n return await this.getCustomResourceResponse(response, responseKey, logicalId, operation);\n }\n\n /**\n * Block until the backing Lambda function for a Custom Resource is in a\n * state that accepts a synchronous Invoke.\n *\n * Two sequential waiters:\n * 1. `waitUntilFunctionActiveV2` — handles the post-CreateFunction\n * `Pending` window (image pull, VPC ENI attachment, layer init).\n * 2. `waitUntilFunctionUpdatedV2` — handles the post-Update\n * `InProgress` window (configuration / code swap settling).\n * Together they cover the only two transient states that reject\n * synchronous Invokes.\n *\n * In the common case (Lambda has been Active for a while, no in-flight\n * Update), both waiters return on first poll → ~2 GetFunction calls →\n * ~200ms overhead. That's the price for correctness; the alternative\n * (whole-stack Active wait at Lambda CREATE) is ~5–10 minutes per\n * VPC-attached function.\n *\n * `serviceToken` is the Lambda function ARN; the Lambda SDK accepts\n * both name and ARN as `FunctionName`, so we pass the ARN through\n * unchanged.\n *\n * `maxWaitTime` is set generously (10 min) because VPC ENI attachment\n * has been observed to take 8+ minutes in pathological cases. The\n * deploy engine's per-resource `--resource-timeout` (default 30 min)\n * still bounds the outer Custom Resource provisioning attempt, so\n * this waiter cap is layered defense, not the only timeout.\n */\n private async waitForBackingLambdaReady(serviceToken: string, logicalId: string): Promise<void> {\n try {\n await waitUntilFunctionActiveV2(\n { client: this.lambdaClient, maxWaitTime: 600 },\n { FunctionName: serviceToken }\n );\n await waitUntilFunctionUpdatedV2(\n { client: this.lambdaClient, maxWaitTime: 600 },\n { FunctionName: serviceToken }\n );\n } catch (error) {\n throw new Error(\n `Lambda backing custom resource ${logicalId} (${serviceToken}) did not reach a ready state for Invoke: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Publish custom resource request to an SNS topic\n */\n private async publishToSns(topicArn: string, request: Record<string, unknown>): Promise<void> {\n await this.snsClient.send(\n new PublishCommand({\n TopicArn: topicArn,\n Message: JSON.stringify(request),\n })\n );\n }\n\n /**\n * Invoke Lambda function synchronously\n */\n private async invokeLambda(\n serviceToken: string,\n request: Record<string, unknown>\n ): Promise<InvocationResponse> {\n return await this.lambdaClient.send(\n new InvokeCommand({\n FunctionName: serviceToken,\n InvocationType: 'RequestResponse',\n Payload: Buffer.from(JSON.stringify(request)),\n })\n );\n }\n\n /**\n * Get custom resource response from either Lambda payload or S3\n *\n * Strategy:\n * 1. If Lambda returned a direct payload with Status field → use it (cfn-response inline)\n * 2. If Lambda returned a payload with PhysicalResourceId → use it (simple handler)\n * 3. Otherwise, poll S3 for the response (cfn-response via ResponseURL)\n */\n private async getCustomResourceResponse(\n lambdaResponse: InvocationResponse,\n responseKey: string,\n logicalId: string,\n operation: string\n ): Promise<CfnCustomResourceResponse> {\n // Check for Lambda execution errors\n if (lambdaResponse.FunctionError) {\n const errorPayload = lambdaResponse.Payload\n ? Buffer.from(lambdaResponse.Payload).toString()\n : 'Unknown';\n throw new Error(`Lambda function error (${lambdaResponse.FunctionError}): ${errorPayload}`);\n }\n\n // Try to parse direct Lambda response\n // Track whether Lambda returned a meaningful payload. If not, this likely indicates\n // an async pattern (e.g., CDK Provider framework with isCompleteHandler that delegates\n // to Step Functions for polling).\n let hasDirectPayload = false;\n try {\n const payload = parseLambdaPayload(lambdaResponse.Payload);\n\n // Check if this is a full cfn-response (has Status field)\n if (\n 'Status' in payload &&\n (payload['Status'] === 'SUCCESS' || payload['Status'] === 'FAILED')\n ) {\n this.logger.debug(`Got direct cfn-response from Lambda for ${logicalId}`);\n await this.cleanupResponseObject(responseKey);\n return payload as unknown as CfnCustomResourceResponse;\n }\n\n // Check if this is a simple handler response (has PhysicalResourceId but no Status)\n if (payload.PhysicalResourceId || payload.Data) {\n this.logger.debug(`Got simple handler response from Lambda for ${logicalId}`);\n await this.cleanupResponseObject(responseKey);\n const result: CfnCustomResourceResponse = {\n Status: 'SUCCESS',\n };\n if (payload.PhysicalResourceId) {\n result.PhysicalResourceId = payload.PhysicalResourceId;\n }\n if (payload.Data) {\n result.Data = payload.Data;\n }\n return result;\n }\n\n // Payload parsed but contained no recognizable fields (e.g., empty object from\n // CDK Provider framework after starting Step Functions). Mark as no direct payload.\n hasDirectPayload = Object.keys(payload).length > 0;\n } catch {\n // Payload parsing failed, try S3\n this.logger.debug(`Lambda payload parse failed for ${logicalId}, checking S3 response`);\n }\n\n // Poll S3 for response (cfn-response module sends to ResponseURL)\n if (!this.responseBucket) {\n this.logger.warn(\n `No response bucket configured for custom resource ${logicalId}. ` +\n `The Lambda handler likely uses cfn-response module which sends to ResponseURL. ` +\n `Configure --state-bucket to enable S3-based response handling.`\n );\n return {\n Status: 'SUCCESS',\n PhysicalResourceId: logicalId,\n };\n }\n\n // Detect async custom resource pattern (CDK Provider framework with isCompleteHandler).\n // When the framework Lambda starts a Step Functions state machine for async polling,\n // it returns no meaningful payload (empty/null). In this case, the Step Functions\n // will eventually PUT the cfn-response to the ResponseURL, which may take up to\n // the configured totalTimeout (default: 1 hour in CDK).\n // We use a longer timeout for this case vs the short timeout for synchronous handlers.\n const isAsyncPattern = !hasDirectPayload;\n if (isAsyncPattern) {\n this.logger.debug(\n `Custom resource ${logicalId} uses async Provider framework. ` +\n `Waiting up to ${Math.round(this.asyncResponseTimeoutMs / 60_000)} minutes.`\n );\n } else {\n this.logger.debug(`Waiting for S3 response from Lambda for ${logicalId} (${operation})`);\n }\n\n const timeoutMs = isAsyncPattern ? this.asyncResponseTimeoutMs : this.SYNC_RESPONSE_TIMEOUT_MS;\n return await this.pollS3Response(responseKey, logicalId, operation, timeoutMs, isAsyncPattern);\n }\n\n /**\n * Prepare a single Custom Resource invocation: generate the request id,\n * derive the S3 response key from it, sign the pre-signed PUT URL for that\n * key, and return all three together.\n *\n * **The request id, response key, and response URL must all be derived from\n * the SAME generation step.** Previously these were generated by separate\n * calls inside `create` / `update` / `delete`, which made it possible for a\n * future refactor (e.g. wrapping URL signing in a retry that re-rolls the\n * id) to silently break the invariant — the Lambda would write to one S3\n * key while cdkd polled a different one, hanging the deploy until the\n * polling timeout (up to 1 hour). See issue #90.\n *\n * Centralising this in one helper makes that invariant impossible to\n * violate at the call sites.\n */\n private async prepareInvocation(): Promise<{\n requestId: string;\n responseKey: string;\n responseURL: string;\n }> {\n const requestId = `cdkd-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n const responseKey = this.getResponseKey(requestId);\n const responseURL = await this.generateResponseURL(responseKey);\n return { requestId, responseKey, responseURL };\n }\n\n /**\n * Generate a pre-signed S3 PUT URL for Lambda to send its response\n */\n private async generateResponseURL(responseKey: string): Promise<string> {\n if (!this.responseBucket) {\n // Fallback: return a dummy URL (legacy behavior)\n return 'https://localhost/cfn-response-not-configured';\n }\n\n // Create an empty placeholder object first (so the key exists for cleanup)\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.responseBucket,\n Key: responseKey,\n Body: '',\n ContentLength: 0,\n ContentType: 'application/json',\n })\n );\n\n // Generate pre-signed PUT URL (valid for 2 hours to accommodate async Provider framework\n // patterns where Step Functions may poll isCompleteHandler for up to 1 hour)\n // Don't specify ContentType so any Content-Type is accepted (cfn-response may send different types)\n const command = new PutObjectCommand({\n Bucket: this.responseBucket,\n Key: responseKey,\n });\n\n const presignedUrl = await getSignedUrl(this.s3Client, command, {\n expiresIn: 7200,\n });\n\n this.logger.debug(\n `Generated pre-signed URL for response: s3://${this.responseBucket}/${responseKey}`\n );\n return presignedUrl;\n }\n\n /**\n * Poll S3 for the custom resource response\n *\n * Uses exponential backoff for polling interval:\n * - Sync mode (standard handlers): starts at 2s, no backoff (short timeout)\n * - Async mode (Provider framework with isCompleteHandler): starts at 2s, backs off to 30s max\n *\n * @param responseKey S3 key where response will be written\n * @param logicalId Logical resource ID for logging\n * @param operation Operation type (Create/Update/Delete) for logging\n * @param timeoutMs Maximum time to wait for response\n * @param useBackoff Whether to use exponential backoff (for async/long-running operations)\n */\n private async pollS3Response(\n responseKey: string,\n logicalId: string,\n operation: string,\n timeoutMs: number = this.SYNC_RESPONSE_TIMEOUT_MS,\n useBackoff: boolean = false\n ): Promise<CfnCustomResourceResponse> {\n const startTime = Date.now();\n let currentInterval = this.INITIAL_POLL_INTERVAL_MS;\n let pollCount = 0;\n\n // Listen for SIGINT to abort polling early\n let interrupted = false;\n const sigintHandler = () => {\n interrupted = true;\n };\n process.on('SIGINT', sigintHandler);\n\n try {\n while (Date.now() - startTime < timeoutMs) {\n if (interrupted) {\n await this.cleanupResponseObject(responseKey);\n process.removeListener('SIGINT', sigintHandler);\n throw new Error(`Custom resource ${logicalId} interrupted by user`);\n }\n\n pollCount++;\n try {\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.responseBucket!,\n Key: responseKey,\n })\n );\n\n const body = await response.Body?.transformToString();\n if (body && body.length > 0) {\n this.logger.debug(`Got S3 response for ${logicalId}: ${body.substring(0, 200)}`);\n\n try {\n const cfnResponse = JSON.parse(body) as CfnCustomResourceResponse;\n\n // Validate response has required fields\n if (cfnResponse.Status === 'SUCCESS' || cfnResponse.Status === 'FAILED') {\n // Cleanup the response object\n await this.cleanupResponseObject(responseKey);\n return cfnResponse;\n }\n } catch {\n // JSON parse failed, response not yet written properly\n this.logger.debug(`S3 response not yet valid JSON for ${logicalId}, retrying...`);\n }\n }\n } catch (error) {\n const err = error as { name?: string };\n if (err.name !== 'NoSuchKey') {\n this.logger.debug(`Error reading S3 response for ${logicalId}: ${err.name}`);\n }\n }\n\n await this.sleep(currentInterval);\n\n // Apply exponential backoff for async patterns (long-running operations)\n if (useBackoff) {\n currentInterval = Math.min(currentInterval * 1.5, this.MAX_POLL_INTERVAL_MS);\n\n // Log progress periodically for long-running operations\n if (pollCount % 10 === 0) {\n const elapsedSec = Math.round((Date.now() - startTime) / 1000);\n this.logger.info(\n `Still waiting for async custom resource ${logicalId} (${operation})... ` +\n `${elapsedSec}s elapsed, polling every ${Math.round(currentInterval / 1000)}s`\n );\n }\n }\n }\n\n // Cleanup on timeout\n await this.cleanupResponseObject(responseKey);\n\n const elapsedMin = Math.round((Date.now() - startTime) / 60_000);\n throw new Error(\n `Timeout waiting for custom resource response for ${logicalId} (${operation}) ` +\n `after ${elapsedMin} minutes. ` +\n (useBackoff\n ? `The async custom resource handler (Provider framework with isCompleteHandler) did not complete within the timeout. ` +\n `Check the Step Functions execution and isCompleteHandler Lambda logs for errors.`\n : `The Lambda handler may not be sending a response to ResponseURL.`)\n );\n } finally {\n process.removeListener('SIGINT', sigintHandler);\n }\n }\n\n /**\n * Get S3 key for response object\n */\n private getResponseKey(requestId: string): string {\n return `${this.responsePrefix}/${requestId}.json`;\n }\n\n /**\n * Cleanup response object from S3\n */\n private async cleanupResponseObject(responseKey: string): Promise<void> {\n if (!this.responseBucket) return;\n\n try {\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.responseBucket,\n Key: responseKey,\n })\n );\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /**\n * Convert property values to strings for CloudFormation compatibility\n *\n * CloudFormation converts all ResourceProperties values to strings before\n * passing them to Lambda handlers. Some CDK internal handlers (like\n * BucketNotificationsHandler) depend on this behavior (e.g., calling .lower()\n * on boolean values).\n */\n private stringifyProperties(properties: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (typeof value === 'boolean') {\n result[key] = String(value);\n } else if (typeof value === 'number') {\n result[key] = String(value);\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n result[key] = this.stringifyProperties(value as Record<string, unknown>);\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Adopt an existing custom resource into cdkd state.\n *\n * **Explicit override only.** A custom resource's identity is the\n * `PhysicalResourceId` returned by its user-supplied Lambda handler at\n * Create time — there is no AWS-side resource cdkd can introspect, no\n * tag API, and no `aws:cdk:path` to look up by. cdkd cannot rediscover\n * a custom resource without invoking the handler, which would mutate\n * state.\n *\n * Users adopting an existing custom resource should pass\n * `--resource <logicalId>=<physicalResourceId>` — the same value the\n * handler returned originally.\n */\n // eslint-disable-next-line @typescript-eslint/require-await -- explicit-override-only intentionally has no AWS calls\n async import(input: ResourceImportInput): Promise<ResourceImportResult | null> {\n if (input.knownPhysicalId) {\n return { physicalId: input.knownPhysicalId, attributes: {} };\n }\n return null;\n }\n}\n","import type { ResourceProvider } from '../types/resource.js';\nimport { CloudControlProvider } from './cloud-control-provider.js';\nimport { CustomResourceProvider } from './providers/custom-resource-provider.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Provider registry for managing resource providers\n *\n * Implements a fallback strategy:\n * 1. Try specific SDK provider if registered for this resource type\n * 2. Fall back to Cloud Control API if resource type is supported\n * 3. Throw error if no provider available\n */\nexport class ProviderRegistry {\n private logger = getLogger().child('ProviderRegistry');\n private providers = new Map<string, ResourceProvider>();\n private cloudControlProvider: CloudControlProvider;\n private customResourceProvider: CustomResourceProvider;\n private skipResourceTypes = new Set<string>();\n\n constructor() {\n this.cloudControlProvider = new CloudControlProvider();\n this.customResourceProvider = new CustomResourceProvider();\n }\n\n /**\n * Configure the response bucket for custom resources\n * This allows Lambda handlers using cfn-response to send responses via S3\n */\n setCustomResourceResponseBucket(bucket: string, bucketRegion?: string): void {\n this.customResourceProvider.setResponseBucket(bucket, bucketRegion);\n this.logger.debug(`Custom resource response bucket set to: ${bucket}`);\n }\n\n /**\n * Register a resource type to be skipped during deployment\n *\n * @param resourceType CloudFormation resource type to skip\n */\n skipResourceType(resourceType: string): void {\n this.logger.debug(`Registering ${resourceType} to be skipped`);\n this.skipResourceTypes.add(resourceType);\n }\n\n /**\n * Register a specific provider for a resource type\n *\n * @param resourceType CloudFormation resource type (e.g., \"AWS::S3::Bucket\")\n * @param provider Provider instance\n */\n register(resourceType: string, provider: ResourceProvider): void {\n this.logger.debug(`Registering provider for ${resourceType}`);\n this.providers.set(resourceType, provider);\n }\n\n /**\n * Unregister a provider for a resource type\n */\n unregister(resourceType: string): void {\n this.logger.debug(`Unregistering provider for ${resourceType}`);\n this.providers.delete(resourceType);\n }\n\n /**\n * Get provider for a resource type\n *\n * Selection strategy:\n * 1. If specific SDK provider is registered, use it\n * 2. Otherwise, use Cloud Control API if supported\n * 3. Throw error if no provider available\n *\n * @param resourceType CloudFormation resource type\n * @returns Provider instance\n * @throws Error if no provider available\n */\n getProvider(resourceType: string): ResourceProvider {\n // 1. Check for specific SDK provider\n const specificProvider = this.providers.get(resourceType);\n if (specificProvider) {\n this.logger.debug(`Using specific SDK provider for ${resourceType}`);\n return specificProvider;\n }\n\n // 2. Check if Cloud Control API supports this resource type\n if (CloudControlProvider.isSupportedResourceType(resourceType)) {\n this.logger.debug(`Using Cloud Control API provider for ${resourceType}`);\n return this.cloudControlProvider;\n }\n\n // 3. Check if it's a custom resource (Custom:: prefix or AWS::CloudFormation::CustomResource)\n if (\n resourceType.startsWith('Custom::') ||\n resourceType === 'AWS::CloudFormation::CustomResource'\n ) {\n this.logger.debug(`Using Custom Resource provider for ${resourceType}`);\n return this.customResourceProvider;\n }\n\n // 4. No provider available\n throw new Error(\n `No provider available for resource type: ${resourceType}. ` +\n `This resource type is not supported by Cloud Control API and no SDK provider is registered.`\n );\n }\n\n /**\n * Check if a resource type should be skipped\n */\n shouldSkipResource(resourceType: string): boolean {\n return this.skipResourceTypes.has(resourceType);\n }\n\n /**\n * Check if a provider is available for a resource type\n */\n hasProvider(resourceType: string): boolean {\n // Skipped resources are considered as \"having a provider\" to avoid validation errors\n if (this.shouldSkipResource(resourceType)) {\n return true;\n }\n return (\n this.providers.has(resourceType) ||\n CloudControlProvider.isSupportedResourceType(resourceType) ||\n resourceType.startsWith('Custom::') ||\n resourceType === 'AWS::CloudFormation::CustomResource'\n );\n }\n\n /**\n * Get the Cloud Control provider instance (for resource state lookup)\n */\n getCloudControlProvider(): CloudControlProvider {\n return this.cloudControlProvider;\n }\n\n /**\n * Get all registered resource types (excluding Cloud Control)\n */\n getRegisteredTypes(): string[] {\n return Array.from(this.providers.keys());\n }\n\n /**\n * Get provider type for a resource type\n *\n * @returns 'sdk' | 'cloud-control' | null\n */\n getProviderType(resourceType: string): 'sdk' | 'cloud-control' | null {\n if (this.providers.has(resourceType)) {\n return 'sdk';\n }\n if (CloudControlProvider.isSupportedResourceType(resourceType)) {\n return 'cloud-control';\n }\n return null;\n }\n\n /**\n * Validate that all resource types have available providers\n *\n * This should be called before deployment starts to ensure all resources can be provisioned.\n *\n * @param resourceTypes Set of resource types to validate\n * @throws Error if any resource type doesn't have a provider\n */\n validateResourceTypes(resourceTypes: Set<string>): void {\n const unsupportedTypes: string[] = [];\n\n for (const resourceType of resourceTypes) {\n if (!this.hasProvider(resourceType)) {\n unsupportedTypes.push(resourceType);\n }\n }\n\n if (unsupportedTypes.length > 0) {\n throw new Error(\n `The following resource types are not supported:\\n` +\n unsupportedTypes.map((type) => ` - ${type}`).join('\\n') +\n `\\n\\nThese resource types are not supported by Cloud Control API and no SDK provider is registered.\\n` +\n `Please report this issue at https://github.com/go-to-k/cdkd/issues so we can add SDK provider support.`\n );\n }\n\n this.logger.debug(\n `Validated ${resourceTypes.size} resource types: all have available providers`\n );\n }\n}\n","/**\n * Shared helpers for `ResourceProvider.import` implementations.\n *\n * Most providers follow the same lookup pattern when adopting an\n * already-deployed resource into cdkd state:\n *\n * 1. If `input.knownPhysicalId` is set (user passed\n * `--resource <logicalId>=<physicalId>`), trust it as ground truth and\n * only fetch attributes.\n * 2. If the template's `properties` carries an explicit name field\n * (`BucketName`, `FunctionName`, `RoleName`, …), use that as the\n * physical id directly.\n * 3. Walk the service's `List*` API and match against the `aws:cdk:path`\n * tag — every CDK-deployed resource carries one.\n *\n * Step 1 + 2 are generic enough to live here. Step 3 needs per-service\n * `List*` + `ListTags*` calls and lives in each provider.\n */\n\nimport type { ResourceImportInput } from '../types/resource.js';\n\n/**\n * Read an explicit name field from template properties. Returns `undefined`\n * when the property is missing or not a string — callers fall back to\n * tag-based lookup in that case.\n */\nexport function readNameProperty(\n input: ResourceImportInput,\n propertyName: string\n): string | undefined {\n const value = input.properties?.[propertyName];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * Resolve the physical id when the template provides an explicit name OR the\n * caller passed `--resource`/`--resource-mapping`. Returns `undefined` when\n * neither shortcut applies — caller must then fall back to tag-based lookup.\n *\n * Does NOT verify the resource exists: callers should follow up with a\n * service-specific `Head*`/`Get*`/`Describe*` to fail fast if the named\n * resource is missing.\n */\nexport function resolveExplicitPhysicalId(\n input: ResourceImportInput,\n nameProperty: string | null\n): string | undefined {\n if (input.knownPhysicalId) return input.knownPhysicalId;\n if (nameProperty) {\n const name = readNameProperty(input, nameProperty);\n if (name) return name;\n }\n return undefined;\n}\n\n/**\n * The standard tag CDK puts on every deployed resource — its construct path\n * within the app, e.g. `MyStack/MyConstruct/MyBucket`. Used as the lookup key\n * when no explicit name is in the template.\n */\nexport const CDK_PATH_TAG = 'aws:cdk:path';\n\n/**\n * Loose tag shape used by every AWS service (`{Key, Value}`).\n *\n * Both Key and Value are typed as `string | undefined` rather than `string?`\n * so this interface accepts AWS SDK v3 `Tag` types verbatim under\n * `exactOptionalPropertyTypes: true` (the SDK types declare them as\n * `Key?: string | undefined`, not `Key?: string`).\n */\nexport interface AwsTag {\n Key?: string | undefined;\n Value?: string | undefined;\n}\n\n/**\n * Match an AWS resource's tag set against the CDK path the template carries.\n * Returns true if the resource was deployed by the same CDK construct.\n */\nexport function matchesCdkPath(tags: readonly AwsTag[] | undefined, cdkPath: string): boolean {\n if (!tags || !cdkPath) return false;\n for (const t of tags) {\n if (t.Key === CDK_PATH_TAG && t.Value === cdkPath) return true;\n }\n return false;\n}\n\n/**\n * Re-shape an AWS tag list (any of the common shapes — array of `{Key, Value}`,\n * map keyed by tag name, or v2-style array of `{TagKey, TagValue}`) into the\n * canonical CFn shape (`Array<{Key, Value}>`) that cdkd state holds, with\n * `aws:`-prefixed entries filtered out.\n *\n * AWS reserves the `aws:` tag prefix; CDK injects `aws:cdk:path` (and\n * sometimes `aws:cdk:metadata`) on every resource it deploys. Those tags are\n * NOT in cdkd state's `Tags` (they come from CDK template `Metadata`, not\n * `Properties.Tags`), so leaving them in the AWS-current snapshot would fire\n * false-positive drift on every CDK-deployed resource.\n *\n * Returns an empty array `[]` when AWS reports no user tags. Callers decide\n * whether to surface `Tags: []` (most providers — matches the typical\n * CFn behavior of always emitting Tags in templates) or omit the key\n * entirely (when the corresponding `create()` only sets Tags when the user\n * explicitly passes them — see each provider's docstring).\n */\nexport function normalizeAwsTagsToCfn(\n tags:\n | readonly AwsTag[]\n | readonly { TagKey?: string | undefined; TagValue?: string | undefined }[]\n | readonly { key?: string | undefined; value?: string | undefined }[]\n | Record<string, string | undefined>\n | undefined\n | null\n): Array<{ Key: string; Value: string }> {\n if (!tags) return [];\n const out: Array<{ Key: string; Value: string }> = [];\n if (Array.isArray(tags)) {\n for (const t of tags) {\n // Support {Key,Value} (most services), {TagKey,TagValue} (RDS/DocDB),\n // and {key,value} (Step Functions / Glue / etc., lower-case).\n const obj = t as Record<string, unknown>;\n const k =\n (typeof obj['Key'] === 'string' ? obj['Key'] : undefined) ??\n (typeof obj['TagKey'] === 'string' ? obj['TagKey'] : undefined) ??\n (typeof obj['key'] === 'string' ? obj['key'] : undefined);\n const v =\n (typeof obj['Value'] === 'string' ? obj['Value'] : undefined) ??\n (typeof obj['TagValue'] === 'string' ? obj['TagValue'] : undefined) ??\n (typeof obj['value'] === 'string' ? obj['value'] : undefined);\n if (typeof k !== 'string' || k.length === 0) continue;\n if (k.startsWith('aws:')) continue;\n out.push({ Key: k, Value: typeof v === 'string' ? v : '' });\n }\n } else {\n for (const [k, v] of Object.entries(tags)) {\n if (!k || k.startsWith('aws:')) continue;\n out.push({ Key: k, Value: typeof v === 'string' ? v : '' });\n }\n }\n // Sort by Key for stable comparison against state (CDK templates\n // produce sorted Tags; AWS API responses are unordered).\n out.sort((a, b) => (a.Key < b.Key ? -1 : a.Key > b.Key ? 1 : 0));\n return out;\n}\n","import {\n IAMClient,\n CreateRoleCommand,\n UpdateRoleCommand,\n UpdateAssumeRolePolicyCommand,\n DeleteRoleCommand,\n GetRoleCommand,\n GetRolePolicyCommand,\n PutRolePolicyCommand,\n DeleteRolePolicyCommand,\n ListRolePoliciesCommand,\n AttachRolePolicyCommand,\n DetachRolePolicyCommand,\n ListAttachedRolePoliciesCommand,\n ListInstanceProfilesForRoleCommand,\n RemoveRoleFromInstanceProfileCommand,\n TagRoleCommand,\n UntagRoleCommand,\n PutRolePermissionsBoundaryCommand,\n DeleteRolePermissionsBoundaryCommand,\n ListRolesCommand,\n ListRoleTagsCommand,\n NoSuchEntityException,\n} from '@aws-sdk/client-iam';\nimport { getLogger } from '../../utils/logger.js';\nimport { getAwsClients } from '../../utils/aws-clients.js';\nimport { ProvisioningError } from '../../utils/error-handler.js';\nimport { assertRegionMatch, type DeleteContext } from '../region-check.js';\nimport { generateResourceNameWithFallback } from '../resource-name.js';\nimport {\n matchesCdkPath,\n normalizeAwsTagsToCfn,\n resolveExplicitPhysicalId,\n} from '../import-helpers.js';\nimport type {\n ResourceProvider,\n ResourceCreateResult,\n ResourceUpdateResult,\n ResourceImportInput,\n ResourceImportResult,\n} from '../../types/resource.js';\n\n/**\n * AWS IAM Role Provider\n *\n * Implements resource provisioning for AWS::IAM::Role using the IAM SDK.\n * This is required because IAM Role is not supported by Cloud Control API.\n */\nexport class IAMRoleProvider implements ResourceProvider {\n private iamClient: IAMClient;\n private logger = getLogger().child('IAMRoleProvider');\n handledProperties = new Map<string, ReadonlySet<string>>([\n [\n 'AWS::IAM::Role',\n new Set([\n 'RoleName',\n 'AssumeRolePolicyDocument',\n 'Description',\n 'MaxSessionDuration',\n 'Path',\n 'PermissionsBoundary',\n 'ManagedPolicyArns',\n 'Policies',\n 'Tags',\n ]),\n ],\n ]);\n\n constructor() {\n // Use global AWS clients manager for better resource management\n const awsClients = getAwsClients();\n this.iamClient = awsClients.iam;\n }\n\n /**\n * Create an IAM role\n */\n async create(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n ): Promise<ResourceCreateResult> {\n this.logger.debug(`Creating IAM role ${logicalId}`);\n\n const roleName = generateResourceNameWithFallback(\n properties['RoleName'] as string | undefined,\n logicalId,\n { maxLength: 64 }\n );\n const assumeRolePolicyDocument = properties['AssumeRolePolicyDocument'];\n\n if (!assumeRolePolicyDocument) {\n throw new ProvisioningError(\n `AssumeRolePolicyDocument is required for IAM role ${logicalId}`,\n resourceType,\n logicalId\n );\n }\n\n try {\n // Serialize policy document\n const policyDocument =\n typeof assumeRolePolicyDocument === 'string'\n ? assumeRolePolicyDocument\n : JSON.stringify(assumeRolePolicyDocument);\n\n // Create role\n const createParams: {\n RoleName: string;\n AssumeRolePolicyDocument: string;\n Description?: string;\n MaxSessionDuration?: number;\n Path?: string;\n PermissionsBoundary?: string;\n } = {\n RoleName: roleName,\n AssumeRolePolicyDocument: policyDocument,\n };\n\n if (properties['Description']) {\n createParams.Description = properties['Description'] as string;\n }\n if (properties['MaxSessionDuration']) {\n createParams.MaxSessionDuration = properties['MaxSessionDuration'] as number;\n }\n if (properties['Path']) {\n createParams.Path = properties['Path'] as string;\n }\n if (properties['PermissionsBoundary']) {\n createParams.PermissionsBoundary = properties['PermissionsBoundary'] as string;\n }\n\n const response = await this.iamClient.send(new CreateRoleCommand(createParams));\n\n this.logger.debug(`Created IAM role: ${roleName}`);\n\n // Attach managed policies if specified\n const managedPolicyArns = properties['ManagedPolicyArns'] as string[] | undefined;\n if (managedPolicyArns && Array.isArray(managedPolicyArns)) {\n for (const policyArn of managedPolicyArns) {\n await this.iamClient.send(\n new AttachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policyArn,\n })\n );\n this.logger.debug(`Attached managed policy ${policyArn} to role ${roleName}`);\n }\n }\n\n // Add inline policies if specified\n const policies = properties['Policies'] as\n | Array<{ PolicyName: string; PolicyDocument: unknown }>\n | undefined;\n if (policies && Array.isArray(policies)) {\n for (const policy of policies) {\n const policyDoc =\n typeof policy.PolicyDocument === 'string'\n ? policy.PolicyDocument\n : JSON.stringify(policy.PolicyDocument);\n\n await this.iamClient.send(\n new PutRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policy.PolicyName,\n PolicyDocument: policyDoc,\n })\n );\n this.logger.debug(`Added inline policy ${policy.PolicyName} to role ${roleName}`);\n }\n }\n\n // Add tags if specified\n const tags = properties['Tags'] as Array<{ Key: string; Value: string }> | undefined;\n if (tags && Array.isArray(tags)) {\n await this.iamClient.send(\n new TagRoleCommand({\n RoleName: roleName,\n Tags: tags,\n })\n );\n this.logger.debug(`Tagged role ${roleName}`);\n }\n\n this.logger.debug(`Successfully created IAM role ${logicalId}: ${roleName}`);\n\n const attributes = {\n Arn: response.Role?.Arn,\n RoleId: response.Role?.RoleId,\n };\n\n return {\n physicalId: roleName,\n attributes,\n };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to create IAM role ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n roleName,\n cause\n );\n }\n }\n\n /**\n * Update an IAM role\n */\n async update(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n previousProperties: Record<string, unknown>\n ): Promise<ResourceUpdateResult> {\n this.logger.debug(`Updating IAM role ${logicalId}: ${physicalId}`);\n\n const newRoleName = generateResourceNameWithFallback(\n properties['RoleName'] as string | undefined,\n logicalId,\n { maxLength: 64 }\n );\n\n // Check if immutable properties changed (requires replacement)\n // RoleName and Path are immutable - cannot be changed after creation\n const newPath = (properties['Path'] as string | undefined) || '/';\n const oldPath = (previousProperties['Path'] as string | undefined) || '/';\n const needsReplacement = newRoleName !== physicalId || newPath !== oldPath;\n\n if (needsReplacement) {\n const reason = newRoleName !== physicalId ? 'RoleName' : 'Path';\n this.logger.debug(\n `${reason} changed, replacing role: ${physicalId} (${reason}: ${reason === 'RoleName' ? `${physicalId} -> ${newRoleName}` : `${oldPath} -> ${newPath}`})`\n );\n\n // Create new role\n const createResult = await this.create(logicalId, resourceType, properties);\n\n // Delete old role with full cleanup (managed policies, inline policies, instance profiles)\n try {\n await this.delete(logicalId, physicalId, resourceType);\n } catch (error) {\n this.logger.warn(\n `Failed to delete old role ${physicalId} during replacement: ${String(error)}. ` +\n `The old role may be orphaned and require manual cleanup.`\n );\n }\n\n const result: ResourceUpdateResult = {\n physicalId: createResult.physicalId,\n wasReplaced: true,\n };\n\n if (createResult.attributes) {\n result.attributes = createResult.attributes;\n }\n\n return result;\n }\n\n try {\n // Update role properties (Description, MaxSessionDuration)\n const updateParams: {\n RoleName: string;\n Description?: string;\n MaxSessionDuration?: number;\n } = {\n RoleName: physicalId,\n };\n\n // `!== undefined` (not truthy) so an empty Description ('') reaches\n // `UpdateRoleCommand`, which the AWS API documents as the way to\n // clear an existing description. A truthy gate would silently drop\n // the empty string and leave the AWS-side description untouched —\n // surfaced as a `cdkd drift --revert` that reports `✓ reverted`\n // but the very next `cdkd drift` re-detects the same drift.\n if (properties['Description'] !== undefined) {\n updateParams.Description = properties['Description'] as string;\n }\n if (properties['MaxSessionDuration'] !== undefined) {\n updateParams.MaxSessionDuration = properties['MaxSessionDuration'] as number;\n }\n\n await this.iamClient.send(new UpdateRoleCommand(updateParams));\n\n // Update AssumeRolePolicyDocument if changed\n const newAssumePolicy = properties['AssumeRolePolicyDocument'];\n const oldAssumePolicy = previousProperties['AssumeRolePolicyDocument'];\n if (newAssumePolicy) {\n const newPolicyStr =\n typeof newAssumePolicy === 'string' ? newAssumePolicy : JSON.stringify(newAssumePolicy);\n const oldPolicyStr = oldAssumePolicy\n ? typeof oldAssumePolicy === 'string'\n ? oldAssumePolicy\n : JSON.stringify(oldAssumePolicy)\n : '';\n\n if (newPolicyStr !== oldPolicyStr) {\n await this.iamClient.send(\n new UpdateAssumeRolePolicyCommand({\n RoleName: physicalId,\n PolicyDocument: newPolicyStr,\n })\n );\n this.logger.debug(`Updated assume role policy for ${physicalId}`);\n }\n }\n\n // Update PermissionsBoundary\n const newBoundary = properties['PermissionsBoundary'] as string | undefined;\n const oldBoundary = previousProperties['PermissionsBoundary'] as string | undefined;\n if (newBoundary !== oldBoundary) {\n if (newBoundary) {\n await this.iamClient.send(\n new PutRolePermissionsBoundaryCommand({\n RoleName: physicalId,\n PermissionsBoundary: newBoundary,\n })\n );\n this.logger.debug(`Set permissions boundary for ${physicalId}: ${newBoundary}`);\n } else if (oldBoundary) {\n await this.iamClient.send(\n new DeleteRolePermissionsBoundaryCommand({\n RoleName: physicalId,\n })\n );\n this.logger.debug(`Removed permissions boundary from ${physicalId}`);\n }\n }\n\n // Update managed policies\n await this.updateManagedPolicies(\n physicalId,\n properties['ManagedPolicyArns'] as string[] | undefined,\n previousProperties['ManagedPolicyArns'] as string[] | undefined\n );\n\n // Update inline policies\n await this.updateInlinePolicies(\n physicalId,\n properties['Policies'] as\n | Array<{ PolicyName: string; PolicyDocument: unknown }>\n | undefined,\n previousProperties['Policies'] as\n | Array<{ PolicyName: string; PolicyDocument: unknown }>\n | undefined\n );\n\n // Update tags\n await this.updateTags(\n physicalId,\n properties['Tags'] as Array<{ Key: string; Value: string }> | undefined,\n previousProperties['Tags'] as Array<{ Key: string; Value: string }> | undefined\n );\n\n this.logger.debug(`Successfully updated IAM role ${logicalId}`);\n\n // Get updated role info\n const getRoleResponse = await this.iamClient.send(\n new GetRoleCommand({ RoleName: physicalId })\n );\n\n const attributes = {\n Arn: getRoleResponse.Role?.Arn,\n RoleId: getRoleResponse.Role?.RoleId,\n };\n\n return {\n physicalId,\n wasReplaced: false,\n attributes,\n };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to update IAM role ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n /**\n * Delete an IAM role\n *\n * Before deleting, performs full cleanup:\n * 1. Detach all managed policies\n * 2. Delete all inline policies\n * 3. Remove role from all instance profiles\n * 4. Delete the role itself\n */\n async delete(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n _properties?: Record<string, unknown>,\n context?: DeleteContext\n ): Promise<void> {\n this.logger.debug(`Deleting IAM role ${logicalId}: ${physicalId}`);\n\n try {\n // Check if role exists\n try {\n await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n const clientRegion = await this.iamClient.config.region();\n assertRegionMatch(\n clientRegion,\n context?.expectedRegion,\n resourceType,\n logicalId,\n physicalId\n );\n this.logger.debug(`Role ${physicalId} does not exist, skipping deletion`);\n return;\n }\n throw error;\n }\n\n // Step 1: Detach all managed policies\n await this.detachAllManagedPolicies(physicalId);\n\n // Step 2: Delete all inline policies\n await this.deleteAllInlinePolicies(physicalId);\n\n // Step 3: Remove role from all instance profiles\n await this.removeFromAllInstanceProfiles(physicalId);\n\n // Step 4: Delete the role\n await this.iamClient.send(new DeleteRoleCommand({ RoleName: physicalId }));\n\n this.logger.debug(`Successfully deleted IAM role ${logicalId}`);\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to delete IAM role ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n /**\n * Detach all managed policies from the role\n */\n private async detachAllManagedPolicies(roleName: string): Promise<void> {\n this.logger.debug(`Detaching all managed policies from role ${roleName}`);\n\n try {\n const attachedPolicies = await this.iamClient.send(\n new ListAttachedRolePoliciesCommand({ RoleName: roleName })\n );\n\n const policies = attachedPolicies.AttachedPolicies || [];\n if (policies.length === 0) {\n this.logger.debug(`No managed policies attached to role ${roleName}`);\n return;\n }\n\n for (const policy of policies) {\n if (policy.PolicyArn) {\n try {\n await this.iamClient.send(\n new DetachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policy.PolicyArn,\n })\n );\n this.logger.debug(`Detached managed policy ${policy.PolicyArn} from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(\n `Managed policy ${policy.PolicyArn} already detached from role ${roleName}`\n );\n } else {\n throw error;\n }\n }\n }\n }\n\n this.logger.debug(`Detached ${policies.length} managed policies from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Role ${roleName} not found when detaching managed policies`);\n return;\n }\n throw error;\n }\n }\n\n /**\n * Delete all inline policies from the role\n */\n private async deleteAllInlinePolicies(roleName: string): Promise<void> {\n this.logger.debug(`Deleting all inline policies from role ${roleName}`);\n\n try {\n const inlinePolicies = await this.iamClient.send(\n new ListRolePoliciesCommand({ RoleName: roleName })\n );\n\n const policyNames = inlinePolicies.PolicyNames || [];\n if (policyNames.length === 0) {\n this.logger.debug(`No inline policies on role ${roleName}`);\n return;\n }\n\n for (const policyName of policyNames) {\n try {\n await this.iamClient.send(\n new DeleteRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policyName,\n })\n );\n this.logger.debug(`Deleted inline policy ${policyName} from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Inline policy ${policyName} already deleted from role ${roleName}`);\n } else {\n throw error;\n }\n }\n }\n\n this.logger.debug(`Deleted ${policyNames.length} inline policies from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Role ${roleName} not found when deleting inline policies`);\n return;\n }\n throw error;\n }\n }\n\n /**\n * Remove the role from all instance profiles\n */\n private async removeFromAllInstanceProfiles(roleName: string): Promise<void> {\n this.logger.debug(`Removing role ${roleName} from all instance profiles`);\n\n try {\n const instanceProfiles = await this.iamClient.send(\n new ListInstanceProfilesForRoleCommand({ RoleName: roleName })\n );\n\n const profiles = instanceProfiles.InstanceProfiles || [];\n if (profiles.length === 0) {\n this.logger.debug(`No instance profiles associated with role ${roleName}`);\n return;\n }\n\n for (const profile of profiles) {\n if (profile.InstanceProfileName) {\n try {\n await this.iamClient.send(\n new RemoveRoleFromInstanceProfileCommand({\n RoleName: roleName,\n InstanceProfileName: profile.InstanceProfileName,\n })\n );\n this.logger.debug(\n `Removed role ${roleName} from instance profile ${profile.InstanceProfileName}`\n );\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(\n `Role ${roleName} already removed from instance profile ${profile.InstanceProfileName}`\n );\n } else {\n throw error;\n }\n }\n }\n }\n\n this.logger.debug(`Removed role ${roleName} from ${profiles.length} instance profiles`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Role ${roleName} not found when removing from instance profiles`);\n return;\n }\n throw error;\n }\n }\n\n /**\n * Update managed policies attached to role\n */\n private async updateManagedPolicies(\n roleName: string,\n newPolicies: string[] | undefined,\n oldPolicies: string[] | undefined\n ): Promise<void> {\n const newSet = new Set(newPolicies || []);\n const oldSet = new Set(oldPolicies || []);\n\n // Attach new policies\n for (const policyArn of newSet) {\n if (!oldSet.has(policyArn)) {\n await this.iamClient.send(\n new AttachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policyArn,\n })\n );\n this.logger.debug(`Attached managed policy ${policyArn}`);\n }\n }\n\n // Detach removed policies\n for (const policyArn of oldSet) {\n if (!newSet.has(policyArn)) {\n await this.iamClient.send(\n new DetachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policyArn,\n })\n );\n this.logger.debug(`Detached managed policy ${policyArn}`);\n }\n }\n }\n\n /**\n * Update inline policies\n */\n private async updateInlinePolicies(\n roleName: string,\n newPolicies: Array<{ PolicyName: string; PolicyDocument: unknown }> | undefined,\n oldPolicies: Array<{ PolicyName: string; PolicyDocument: unknown }> | undefined\n ): Promise<void> {\n const newMap = new Map((newPolicies || []).map((p) => [p.PolicyName, p.PolicyDocument]));\n const oldMap = new Map((oldPolicies || []).map((p) => [p.PolicyName, p.PolicyDocument]));\n\n // Add or update policies\n for (const [policyName, policyDoc] of newMap) {\n const policyDocument = typeof policyDoc === 'string' ? policyDoc : JSON.stringify(policyDoc);\n\n await this.iamClient.send(\n new PutRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policyName,\n PolicyDocument: policyDocument,\n })\n );\n this.logger.debug(`Updated inline policy ${policyName}`);\n }\n\n // Delete removed policies\n for (const policyName of oldMap.keys()) {\n if (!newMap.has(policyName)) {\n await this.iamClient.send(\n new DeleteRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policyName,\n })\n );\n this.logger.debug(`Deleted inline policy ${policyName}`);\n }\n }\n }\n\n /**\n * Update tags on the role\n */\n private async updateTags(\n roleName: string,\n newTags: Array<{ Key: string; Value: string }> | undefined,\n oldTags: Array<{ Key: string; Value: string }> | undefined\n ): Promise<void> {\n const newTagMap = new Map((newTags || []).map((t) => [t.Key, t.Value]));\n const oldTagMap = new Map((oldTags || []).map((t) => [t.Key, t.Value]));\n\n // Find tags to remove (present in old but not in new)\n const tagsToRemove: string[] = [];\n for (const key of oldTagMap.keys()) {\n if (!newTagMap.has(key)) {\n tagsToRemove.push(key);\n }\n }\n\n // Find tags to add/update (new or changed value)\n const tagsToAdd: Array<{ Key: string; Value: string }> = [];\n for (const [key, value] of newTagMap) {\n if (oldTagMap.get(key) !== value) {\n tagsToAdd.push({ Key: key, Value: value });\n }\n }\n\n if (tagsToRemove.length > 0) {\n await this.iamClient.send(\n new UntagRoleCommand({\n RoleName: roleName,\n TagKeys: tagsToRemove,\n })\n );\n this.logger.debug(`Removed ${tagsToRemove.length} tags from role ${roleName}`);\n }\n\n if (tagsToAdd.length > 0) {\n await this.iamClient.send(\n new TagRoleCommand({\n RoleName: roleName,\n Tags: tagsToAdd,\n })\n );\n this.logger.debug(`Added/updated ${tagsToAdd.length} tags on role ${roleName}`);\n }\n }\n\n /**\n * Resolve a single `Fn::GetAtt` attribute for an existing IAM role.\n *\n * CloudFormation's `AWS::IAM::Role` exposes `Arn` and `RoleId`; both are\n * available from the `GetRole` response. See:\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role-return-values\n *\n * Used by `cdkd orphan` to live-fetch attribute values that need to be\n * substituted into sibling references.\n */\n async getAttribute(\n physicalId: string,\n _resourceType: string,\n attributeName: string\n ): Promise<unknown> {\n try {\n const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));\n switch (attributeName) {\n case 'Arn':\n return resp.Role?.Arn;\n case 'RoleId':\n return resp.Role?.RoleId;\n default:\n return undefined;\n }\n } catch (err) {\n if (err instanceof NoSuchEntityException) return undefined;\n throw err;\n }\n }\n\n /**\n * Read the AWS-current IAM role configuration in CFn-property shape.\n *\n * Issues `GetRole` for the top-level role configuration and\n * `ListRolePolicies` + `ListAttachedRolePolicies` for inline / managed\n * policy *names*. AWS URL-decodes `AssumeRolePolicyDocument` for us\n * when it surfaces — we re-parse it as JSON so the comparator can match\n * against state's already-parsed object.\n *\n * Coverage and shape decisions:\n * - `RoleName`, `Description`, `MaxSessionDuration`, `Path` — straight\n * from `Role.*`.\n * - `PermissionsBoundary` — emitted as `'' ` placeholder when AWS has\n * none, so a console-side ADD on a role that was deployed without a\n * boundary surfaces as drift. (The drift comparator's top-level walk\n * is state-keys-only; without the always-emit placeholder a fresh\n * `PermissionsBoundary` on the AWS side would never enter\n * `observedProperties` and the comparator would silently ignore it.)\n * - `AssumeRolePolicyDocument` — `Role.AssumeRolePolicyDocument` is a\n * URL-encoded JSON string; we URL-decode + JSON-parse so cdkd state's\n * object form compares cleanly. (Both shapes — string and object — are\n * accepted by `create()`, but state typically stores the parsed object\n * after intrinsic resolution.)\n * - `ManagedPolicyArns` — array of ARN strings from\n * `ListAttachedRolePolicies`.\n * - `Policies` — inline policies surfaced as `[{PolicyName, PolicyDocument}]`.\n * `ListRolePolicies` for names + `GetRolePolicy` per name for the\n * body (URL-decoded + JSON-parsed). Ordering is reconciled against\n * state's `Policies` array (when supplied via the `properties`\n * parameter) so a state-vs-AWS positional compare doesn't fire false\n * drift purely from `ListRolePolicies` returning lexicographic order;\n * AWS-only policies (added via console) are appended at the end so\n * they still surface as drift via length / content mismatch.\n * - `Tags` is surfaced via `ListRoleTags` (paginated). CDK's `aws:*`\n * auto-tags are filtered out by `normalizeAwsTagsToCfn` so they don't\n * fire false-positive drift; always emitted (even when empty) so a\n * console-side tag ADD on an originally-untagged role surfaces as\n * drift on the v3 observedProperties baseline.\n *\n * Returns `undefined` when the role is gone (`NoSuchEntityException`).\n */\n async readCurrentState(\n physicalId: string,\n _logicalId: string,\n _resourceType: string,\n properties?: Record<string, unknown>,\n context?: import('../../types/resource.js').ReadCurrentStateContext\n ): Promise<Record<string, unknown> | undefined> {\n let role;\n try {\n const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));\n role = resp.Role;\n } catch (err) {\n if (err instanceof NoSuchEntityException) return undefined;\n throw err;\n }\n if (!role) return undefined;\n\n const result: Record<string, unknown> = {};\n\n if (role.RoleName !== undefined) result['RoleName'] = role.RoleName;\n result['Description'] = role.Description ?? '';\n if (role.MaxSessionDuration !== undefined) {\n result['MaxSessionDuration'] = role.MaxSessionDuration;\n }\n if (role.Path !== undefined) result['Path'] = role.Path;\n // Always-emit (PR #145 pattern): surfaces console-side ADDs on roles\n // deployed without a boundary. AWS returns the boundary as a nested\n // `{ PermissionsBoundaryArn, PermissionsBoundaryType }` shape; cdkd\n // state stores the bare ARN string (matches CFn input shape).\n result['PermissionsBoundary'] = role.PermissionsBoundary?.PermissionsBoundaryArn ?? '';\n if (role.AssumeRolePolicyDocument) {\n // GetRole returns AssumeRolePolicyDocument URL-encoded. Decode and\n // parse so the comparator can match cdkd state (which holds the\n // already-resolved object form).\n try {\n result['AssumeRolePolicyDocument'] = JSON.parse(\n decodeURIComponent(role.AssumeRolePolicyDocument)\n ) as unknown;\n } catch {\n // Fall back to the raw string if decoding / parsing fails. The\n // comparator handles primitive vs object mismatches correctly.\n result['AssumeRolePolicyDocument'] = role.AssumeRolePolicyDocument;\n }\n }\n\n // ManagedPolicyArns — string[] of attached managed policy ARNs.\n try {\n const attached = await this.iamClient.send(\n new ListAttachedRolePoliciesCommand({ RoleName: physicalId })\n );\n const arns = (attached.AttachedPolicies ?? [])\n .map((p) => p.PolicyArn)\n .filter((arn): arn is string => !!arn);\n result['ManagedPolicyArns'] = arns;\n } catch (err) {\n if (!(err instanceof NoSuchEntityException)) throw err;\n }\n\n // Inline Policies — `[{PolicyName, PolicyDocument}]`. Cap at IAM's\n // documented 10-inline-policies-per-role limit to bound the API\n // budget; ListRolePolicies is paginated for forward-compat anyway.\n try {\n const policyNames: string[] = [];\n let policyMarker: string | undefined;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const listResp = await this.iamClient.send(\n new ListRolePoliciesCommand({\n RoleName: physicalId,\n ...(policyMarker ? { Marker: policyMarker } : {}),\n })\n );\n for (const name of listResp.PolicyNames ?? []) policyNames.push(name);\n if (!listResp.IsTruncated) break;\n policyMarker = listResp.Marker;\n }\n\n // Issue #323: filter out inline policies that are managed by a\n // SEPARATE `AWS::IAM::Policy` resource attached via `Roles: [role]`.\n // CDK's `iam.Policy({ roles: [r] })` (and L2 helpers like\n // `taskRole.addToPolicy(...)` / `bucket.grantRead(role)` /\n // `ContainerImage.fromEcrRepository(repo)`'s execution-role grant)\n // creates a separate `AWS::IAM::Policy` resource — which AWS\n // implements via `iam:PutRolePolicy`, so those inline policies\n // appear in `ListRolePolicies` output. Without this filter every\n // such Role fires false drift (state.Policies = [] vs aws =\n // [{...DefaultPolicy*}]).\n const managedByOtherResource = collectInlinePolicyNamesManagedBySiblings(\n physicalId,\n context,\n 'Roles'\n );\n const filteredNames = policyNames.filter((n) => !managedByOtherResource.has(n));\n\n // Fetch every body in parallel (max 10; well under any IAM rate\n // limit). URL-decode + JSON-parse so the comparator sees the same\n // object shape state holds after intrinsic resolution.\n const bodies = new Map<string, unknown>();\n await Promise.all(\n filteredNames.map(async (name) => {\n const resp = await this.iamClient.send(\n new GetRolePolicyCommand({ RoleName: physicalId, PolicyName: name })\n );\n if (!resp.PolicyDocument) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(decodeURIComponent(resp.PolicyDocument));\n } catch {\n parsed = resp.PolicyDocument;\n }\n bodies.set(name, parsed);\n })\n );\n\n // Reconcile order against state's `Policies` so a positional array\n // compare doesn't fire purely from `ListRolePolicies` returning\n // lexicographic order. AWS-only entries (console adds) tail-append\n // so length / content mismatch still surfaces them as drift.\n const statePolicies =\n (properties?.['Policies'] as Array<{ PolicyName?: string }> | undefined) ?? [];\n const remaining = new Set(bodies.keys());\n const inline: Array<{ PolicyName: string; PolicyDocument: unknown }> = [];\n for (const sp of statePolicies) {\n const name = sp?.PolicyName;\n if (typeof name !== 'string') continue;\n if (bodies.has(name)) {\n inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });\n remaining.delete(name);\n }\n }\n for (const name of [...remaining].sort()) {\n inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });\n }\n result['Policies'] = inline;\n } catch (err) {\n if (!(err instanceof NoSuchEntityException)) throw err;\n }\n\n // Tags via ListRoleTags. Paginated — small page sizes are fine since\n // IAM enforces a 50-tag-per-role limit, but we still iterate Marker for\n // forward-compat.\n try {\n const collected: Array<{ Key?: string | undefined; Value?: string | undefined }> = [];\n let marker: string | undefined;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const tagsResp = await this.iamClient.send(\n new ListRoleTagsCommand({\n RoleName: physicalId,\n ...(marker ? { Marker: marker } : {}),\n })\n );\n if (tagsResp.Tags) {\n for (const t of tagsResp.Tags) {\n collected.push({ Key: t.Key, Value: t.Value });\n }\n }\n if (!tagsResp.IsTruncated) break;\n marker = tagsResp.Marker;\n }\n const tags = normalizeAwsTagsToCfn(collected);\n result['Tags'] = tags;\n } catch (err) {\n if (!(err instanceof NoSuchEntityException)) throw err;\n }\n\n return result;\n }\n\n /**\n * Adopt an existing IAM role into cdkd state.\n *\n * Lookup order:\n * 1. `--resource` override or `Properties.RoleName` → use directly,\n * verify via `GetRole`.\n * 2. `ListRoles` + `ListRoleTags`, match `aws:cdk:path` tag.\n *\n * `ListRoles` is paginated and IAM is global (no region scoping), so this\n * walks every role in the account once. Acceptable for the cardinalities\n * we expect (typically <100 roles per account); larger accounts may want\n * to provide `--resource` overrides instead.\n */\n async import(input: ResourceImportInput): Promise<ResourceImportResult | null> {\n const explicit = resolveExplicitPhysicalId(input, 'RoleName');\n if (explicit) {\n try {\n await this.iamClient.send(new GetRoleCommand({ RoleName: explicit }));\n return { physicalId: explicit, attributes: {} };\n } catch (err) {\n if (err instanceof NoSuchEntityException) return null;\n throw err;\n }\n }\n\n if (!input.cdkPath) return null;\n\n let marker: string | undefined;\n do {\n const list = await this.iamClient.send(\n new ListRolesCommand({ ...(marker && { Marker: marker }) })\n );\n for (const role of list.Roles ?? []) {\n if (!role.RoleName) continue;\n try {\n const tags = await this.iamClient.send(\n new ListRoleTagsCommand({ RoleName: role.RoleName })\n );\n if (matchesCdkPath(tags.Tags, input.cdkPath)) {\n return { physicalId: role.RoleName, attributes: {} };\n }\n } catch (err) {\n if (err instanceof NoSuchEntityException) continue;\n throw err;\n }\n }\n marker = list.IsTruncated ? list.Marker : undefined;\n } while (marker);\n return null;\n }\n}\n\n/**\n * Issue #323: build the set of inline-policy names that are managed by\n * a sibling `AWS::IAM::Policy` resource in the same stack via the given\n * attachment field (`Roles` / `Users` / `Groups`). cdkd's IAM Role /\n * User / Group `readCurrentState` helpers exclude these from\n * `ListRolePolicies` / `ListUserPolicies` / `ListGroupPolicies` output\n * to avoid false drift — the inline policy is faithfully managed by\n * the sibling `AWS::IAM::Policy` resource, not the role/user/group\n * itself. The CDK patterns that produce this shape are pervasive:\n * `role.addToPolicy(...)`, `taskRole.addToPolicy(...)`,\n * `bucket.grantRead(role)`, `ContainerImage.fromEcrRepository(repo)`'s\n * execution-role grant, every L2-construct's auto-emitted `Default\n * Policy*`.\n *\n * @param targetPhysicalId The physicalId of the role/user/group being\n * read (matches values in the sibling's\n * `Properties.Roles` / `Users` / `Groups`).\n * @param context Cross-resource context (may be `undefined`\n * for callers that don't supply it — e.g.\n * deploy-time observed-capture before state\n * is complete; the filter then no-ops which\n * is safe because the sibling's\n * `PutRolePolicy` hasn't fired yet at that\n * point).\n * @param attachmentField Which sibling field to inspect: `'Roles'`,\n * `'Users'`, or `'Groups'`.\n * @returns Set of `PolicyName` values to exclude. Empty when no\n * sibling matches OR when context is undefined.\n */\nexport function collectInlinePolicyNamesManagedBySiblings(\n targetPhysicalId: string,\n context: import('../../types/resource.js').ReadCurrentStateContext | undefined,\n attachmentField: 'Roles' | 'Users' | 'Groups'\n): Set<string> {\n const result = new Set<string>();\n const siblings = context?.siblings;\n if (!siblings) return result;\n for (const sibling of Object.values(siblings)) {\n if (sibling.resourceType !== 'AWS::IAM::Policy') continue;\n const attachments = sibling.properties[attachmentField];\n if (!Array.isArray(attachments)) continue;\n if (!attachments.some((a) => a === targetPhysicalId)) continue;\n const name = sibling.properties['PolicyName'];\n if (typeof name === 'string') result.add(name);\n }\n return result;\n}\n","import { getLogger } from '../utils/logger.js';\n\nexport type DagNodeState = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';\n\nexport interface DagNode<T = unknown> {\n id: string;\n dependencies: Set<string>;\n state: DagNodeState;\n data: T;\n}\n\n/**\n * Event-driven DAG executor with bounded concurrency.\n *\n * Dispatches a node as soon as ALL of its dependencies are completed —\n * unlike level-synchronized execution, downstream work does not wait for\n * unrelated siblings in the same \"level\" to finish.\n *\n * Failure handling:\n * - A failed node marks its transitive downstream as 'skipped' (not started)\n * - In-flight nodes drain naturally; no new dispatch after first failure\n * (cancelled() can be set to halt dispatch — used for SIGINT)\n * - On drain, rejects with the FIRST failure (matches prior behavior)\n *\n * Cancellation:\n * - When cancelled() returns true, no new nodes are started.\n * In-flight nodes complete normally. After drain, resolves cleanly\n * if no errors — caller is responsible for translating cancellation\n * into a thrown error (e.g., InterruptedError on SIGINT).\n *\n * Dependencies pointing to nodes outside the registered set are treated\n * as already-completed (e.g., NO_CHANGE resources excluded from the DAG).\n */\nexport class DagExecutor<T = unknown> {\n private nodes = new Map<string, DagNode<T>>();\n private logger = getLogger().child('DagExecutor');\n\n add(node: DagNode<T>): void {\n this.nodes.set(node.id, node);\n }\n\n has(id: string): boolean {\n return this.nodes.has(id);\n }\n\n size(): number {\n return this.nodes.size;\n }\n\n values(): IterableIterator<DagNode<T>> {\n return this.nodes.values();\n }\n\n async execute(\n concurrency: number,\n fn: (node: DagNode<T>) => Promise<void>,\n cancelled: () => boolean = () => false\n ): Promise<void> {\n let active = 0;\n const errors: Array<{ id: string; error: unknown }> = [];\n\n return new Promise<void>((resolve, reject) => {\n const dispatch = (): void => {\n // Mark nodes whose dependencies failed/skipped as skipped — to a\n // fixed point, so transitive dependents propagate within a single\n // dispatch (e.g., A→B→C where A failed must mark BOTH B and C as\n // skipped, regardless of node insertion order).\n let changed = true;\n while (changed) {\n changed = false;\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const hasFailedDep = [...node.dependencies].some((depId) => {\n const dep = this.nodes.get(depId);\n return dep && (dep.state === 'failed' || dep.state === 'skipped');\n });\n if (hasFailedDep) {\n node.state = 'skipped';\n changed = true;\n this.logger.debug(`Skipped ${node.id}: dependency failed or was skipped`);\n }\n }\n }\n\n // Find ready nodes (deps completed or external-to-DAG).\n const ready: DagNode<T>[] = [];\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const depsReady = [...node.dependencies].every((depId) => {\n const dep = this.nodes.get(depId);\n return !dep || dep.state === 'completed';\n });\n if (depsReady) ready.push(node);\n }\n\n // Dispatch up to concurrency limit, unless cancellation requested.\n if (!cancelled()) {\n for (const node of ready) {\n if (active >= concurrency) break;\n node.state = 'running';\n active++;\n\n fn(node)\n .then(() => {\n node.state = 'completed';\n })\n .catch((error) => {\n node.state = 'failed';\n errors.push({ id: node.id, error });\n })\n .finally(() => {\n active--;\n dispatch();\n });\n }\n }\n\n if (active === 0) {\n // Drain-before-reject guarantee: we only reach this point after every\n // in-flight node has settled (success OR failure), because each fn()\n // promise's .finally() decrements `active` and re-runs dispatch. So\n // when a node fails early, sibling nodes already running are allowed\n // to complete normally — their successful completion is visible to\n // the caller (e.g., for state-save and rollback bookkeeping) BEFORE\n // execute() rejects. Don't change to \"reject as soon as errors[] is\n // non-empty\" without revisiting the deploy-engine catch path.\n if (errors.length > 0) {\n reject(errors[0]!.error);\n return;\n }\n const stillPending = [...this.nodes.values()].some((n) => n.state === 'pending');\n if (stillPending && !cancelled()) {\n const pending = [...this.nodes.values()]\n .filter((n) => n.state === 'pending')\n .map((n) => n.id);\n reject(\n new Error(\n `Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies (${pending.join(', ')})`\n )\n );\n return;\n }\n resolve();\n }\n };\n\n dispatch();\n });\n }\n}\n","/**\n * Type-based implicit deletion dependency rules.\n *\n * CloudFormation expresses creation order via Ref / Fn::GetAtt / DependsOn.\n * For deletion, AWS additionally enforces ordering rules that aren't visible\n * in those references — for example, an InternetGateway can't be deleted\n * while it's still attached to a VPC, even though the attachment Ref's the\n * IGW (not the other way around). This module centralizes those type-based\n * rules so that both the deploy engine (DELETE phase) and the destroy\n * command apply the same ordering.\n *\n * Each entry maps `KEY` → list of types that must be deleted BEFORE the\n * KEY type. Reading example:\n *\n * 'AWS::EC2::Subnet': ['AWS::Lambda::Function']\n *\n * = \"every Subnet in this stack must be deleted AFTER every Lambda in\n * this stack\" — required because Lambda's VpcConfig leaves an ENI in\n * the subnet for some time after the function is deleted, and tearing\n * the subnet down first triggers a DependencyViolation.\n */\nexport const IMPLICIT_DELETE_DEPENDENCIES: Record<string, readonly string[]> = {\n // IGW must be deleted AFTER VPCGatewayAttachment\n 'AWS::EC2::InternetGateway': ['AWS::EC2::VPCGatewayAttachment'],\n\n // EventBus must be deleted AFTER Rules on that bus\n 'AWS::Events::EventBus': ['AWS::Events::Rule'],\n\n // Athena workgroup must be deleted AFTER its named queries\n 'AWS::Athena::WorkGroup': ['AWS::Athena::NamedQuery'],\n\n // CloudFront managed-policy-style resources must be deleted AFTER\n // any Distribution that references them\n 'AWS::CloudFront::ResponseHeadersPolicy': ['AWS::CloudFront::Distribution'],\n 'AWS::CloudFront::CachePolicy': ['AWS::CloudFront::Distribution'],\n 'AWS::CloudFront::OriginAccessControl': ['AWS::CloudFront::Distribution'],\n\n // VPC must be deleted AFTER all VPC-dependent resources\n 'AWS::EC2::VPC': [\n 'AWS::EC2::Subnet',\n 'AWS::EC2::SecurityGroup',\n 'AWS::EC2::InternetGateway',\n 'AWS::EC2::EgressOnlyInternetGateway',\n 'AWS::EC2::VPCGatewayAttachment',\n 'AWS::EC2::RouteTable',\n ],\n\n // Subnet must be deleted AFTER any Lambda that may still hold an ENI\n // in it. Lambda DELETE returns immediately but the ENI is detached\n // asynchronously by AWS, so deleting the Subnet first races the detach\n // and yields \"DependencyViolation\".\n 'AWS::EC2::Subnet': ['AWS::EC2::SubnetRouteTableAssociation', 'AWS::Lambda::Function'],\n\n // RouteTable must be deleted AFTER Route and Association\n 'AWS::EC2::RouteTable': ['AWS::EC2::Route', 'AWS::EC2::SubnetRouteTableAssociation'],\n\n // SecurityGroup must be deleted AFTER any Lambda whose ENI is bound\n // to it (same ENI-detach race as Subnet above).\n 'AWS::EC2::SecurityGroup': [\n 'AWS::EC2::SecurityGroupIngress',\n 'AWS::EC2::SecurityGroupEgress',\n 'AWS::Lambda::Function',\n ],\n};\n","/**\n * Patterns that mark an AWS error as a transient/retryable failure.\n * Each entry is a substring match against the error message; all of these\n * are situations where the same call typically succeeds after a short delay\n * because of eventual consistency or just-created-dependency propagation.\n */\nexport const RETRYABLE_ERROR_MESSAGE_PATTERNS: readonly string[] = [\n // IAM propagation\n 'cannot be assumed',\n 'role defined for the function',\n 'not authorized to perform',\n 'execution role',\n 'trust policy',\n 'Role validation failed',\n 'does not have required permissions',\n 'Trusted Entity',\n 'currently in the following state: Pending',\n // DELETE dependency ordering (parallel deletion race conditions)\n 'has dependencies and cannot be deleted',\n \"can't be deleted since it has\",\n 'DependencyViolation',\n // AWS eventual consistency (dependency just created but not yet visible)\n // e.g., RDS DBCluster referencing a just-created DBSubnetGroup\n 'does not exist',\n // AppSync schema is being created asynchronously\n 'Schema is currently being altered',\n // IAM principal not yet propagated to S3 bucket policy\n 'Invalid principal in policy',\n // S3 bucket creation/deletion still in progress\n 'conflicting conditional operation',\n // Secrets Manager: ForceDeleteWithoutRecovery may take a moment to propagate\n 'scheduled for deletion',\n // DynamoDB Streams / Kinesis: IAM role not yet propagated\n 'Cannot access stream',\n 'Please ensure the role can perform',\n // KMS: IAM role not yet propagated for CreateGrant\n 'KMS key is invalid for CreateGrant',\n // CloudWatch Logs SubscriptionFilter: Kinesis stream eventual consistency\n // or SubscriptionFilter role propagation. CW Logs probes the destination\n // by delivering a test message; if the stream is freshly ACTIVE or the\n // assumed role hasn't propagated, the probe fails with \"Invalid request\".\n 'Could not deliver test message',\n // SQS: same-name queue can't be re-created until 60s after a delete.\n // Hits when a stack is destroyed and re-deployed in quick succession\n // (a common dev / iteration loop). Retry recovers within ~60s instead\n // of failing the whole deploy.\n 'wait 60 seconds',\n];\n\n/**\n * HTTP status codes that always indicate a transient failure worth retrying.\n * 429 = Too Many Requests (throttle), 503 = Service Unavailable.\n */\nexport const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number> = new Set([429, 503]);\n\n/**\n * Determine whether an AWS error should be retried.\n *\n * Checks (in order):\n * 1. HTTP status code on the error itself (`$metadata.httpStatusCode`)\n * 2. HTTP status code on a wrapped cause (`cause.$metadata.httpStatusCode`)\n * 3. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}\n */\nexport function isRetryableTransientError(error: unknown, message: string): boolean {\n const metadata = (error as { $metadata?: { httpStatusCode?: number } }).$metadata;\n const statusCode = metadata?.httpStatusCode;\n if (statusCode !== undefined && RETRYABLE_HTTP_STATUS_CODES.has(statusCode)) return true;\n\n const cause = (error as { cause?: { $metadata?: { httpStatusCode?: number } } }).cause;\n const causeStatus = cause?.$metadata?.httpStatusCode;\n if (causeStatus !== undefined && RETRYABLE_HTTP_STATUS_CODES.has(causeStatus)) return true;\n\n return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));\n}\n","/**\n * Retry helper for resource provisioning operations that hit transient\n * AWS eventual-consistency errors (IAM propagation, Lambda Pending state,\n * dependency violations, etc.).\n *\n * Extracted from DeployEngine so the backoff schedule can be unit-tested\n * in isolation. The retryable-error classifier itself lives in\n * `./retryable-errors.ts`.\n */\n\nimport { isRetryableTransientError } from './retryable-errors.js';\n\nexport interface RetryLogger {\n debug(message: string): void;\n}\n\nexport interface WithRetryOptions {\n /** Max number of retries after the first attempt. Defaults to 8. */\n maxRetries?: number;\n /**\n * Initial backoff in milliseconds. Subsequent retries double it\n * (1s -> 2s -> 4s -> ... at the default of 1_000ms).\n *\n * The default of 1_000ms is tuned for the typical AWS eventual-consistency\n * window of 2-5s (IAM trust-policy propagation, freshly-created Lambda\n * leaving Pending state). A longer initial delay (e.g. 10s) adds idle time\n * on the deploy critical path even when the underlying window is much\n * shorter.\n */\n initialDelayMs?: number;\n /**\n * Cap for the per-retry delay in milliseconds. Once the doubling schedule\n * reaches this value it stays flat instead of growing further. Defaults to\n * 8_000ms.\n *\n * Why cap: IAM propagation has a long-ish tail (occasional 20-30s waits\n * past the typical 2-5s window). Pure exponential backoff turns a single\n * stalled propagation into 16s, 32s, 64s waits — far more than the\n * underlying window. Capping at 8s lets us still poll roughly every 8s\n * once we're past the early ramp-up, recovering as soon as AWS stabilises.\n */\n maxDelayMs?: number;\n /** Optional debug logger; receives one line per retry attempt. */\n logger?: RetryLogger;\n /**\n * Optional interrupt check — invoked once per second while sleeping.\n * Throws an interrupt error (e.g. on SIGINT) to abort the retry loop early.\n */\n isInterrupted?: () => boolean;\n /** Thrown when `isInterrupted()` returns true mid-sleep. */\n onInterrupted?: () => Error;\n /** Override the sleep implementation (used by tests to skip real waits). */\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Run `operation`, retrying transient failures with exponential backoff\n * capped at `maxDelayMs`.\n *\n * Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):\n * 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)\n *\n * Non-retryable errors are rethrown immediately. The transient-error\n * classifier is `isRetryableTransientError` from ./retryable-errors.ts.\n */\nexport async function withRetry<T>(\n operation: () => Promise<T>,\n logicalId: string,\n opts: WithRetryOptions = {}\n): Promise<T> {\n const maxRetries = opts.maxRetries ?? 8;\n const initialDelayMs = opts.initialDelayMs ?? 1_000;\n const maxDelayMs = opts.maxDelayMs ?? 8_000;\n const sleep = opts.sleep ?? defaultSleep;\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message : String(error);\n\n const retryable = isRetryableTransientError(error, message);\n if (!retryable || attempt >= maxRetries) {\n throw error;\n }\n\n const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);\n opts.logger?.debug(\n ` ⏳ Retrying ${logicalId} in ${delay / 1000}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`\n );\n\n // Interruptible sleep: check for SIGINT every second during delay.\n for (let waited = 0; waited < delay; waited += 1000) {\n if (opts.isInterrupted?.()) {\n throw opts.onInterrupted ? opts.onInterrupted() : new Error('Interrupted');\n }\n await sleep(Math.min(1000, delay - waited));\n }\n }\n }\n\n throw lastError;\n}\n","/**\n * Per-resource wall-clock deadline + warn timer for provider operations.\n *\n * Wraps a single provider call (CREATE / UPDATE / DELETE) so the deploy\n * engine can enforce `--resource-timeout` and `--resource-warn-after`\n * without each provider needing to plumb timeouts through itself.\n *\n * Mechanism:\n * - A `setTimeout` fires `onWarn(elapsedMs)` once at `warnAfterMs`.\n * - A `setTimeout` fires `onTimeout(elapsedMs)` once at `timeoutMs` and\n * causes the wrapper's outer promise to reject with the error returned\n * by `onTimeout`.\n * - When the wrapped operation settles first, both timers are cleared\n * and neither callback fires.\n *\n * Caveat: this is a `Promise.race`-style abort, not a true cancellation.\n * The underlying provider call keeps running for some additional time\n * after the timer fires — that is documented and accepted; threading\n * `AbortController` through every provider is out of scope for v1.\n */\nexport interface ResourceDeadlineOptions {\n /** Milliseconds after which to fire `onWarn` once. */\n warnAfterMs: number;\n /** Milliseconds after which to abort with `onTimeout`. */\n timeoutMs: number;\n /**\n * Called once when the operation has been running longer than\n * `warnAfterMs`. Receives the elapsed milliseconds (≈ `warnAfterMs`).\n * No-op default; callers typically mutate the live renderer's task\n * label and emit a `logger.warn` line.\n */\n onWarn?: (elapsedMs: number) => void;\n /**\n * Called when the operation exceeds `timeoutMs`. Must return the\n * `Error` to reject the outer promise with. Receives elapsed\n * milliseconds (≈ `timeoutMs`).\n */\n onTimeout: (elapsedMs: number) => Error;\n}\n\n/**\n * Validation error thrown synchronously when option values are nonsensical\n * (`timeoutMs <= warnAfterMs`, non-positive, NaN). Keeps the helper safe\n * to use even in tests that pass raw numbers.\n */\nexport class InvalidResourceDeadlineError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidResourceDeadlineError';\n }\n}\n\nfunction validateOptions(opts: ResourceDeadlineOptions): void {\n const { warnAfterMs, timeoutMs } = opts;\n if (\n !Number.isFinite(warnAfterMs) ||\n !Number.isFinite(timeoutMs) ||\n warnAfterMs <= 0 ||\n timeoutMs <= 0\n ) {\n throw new InvalidResourceDeadlineError(\n `withResourceDeadline: warnAfterMs and timeoutMs must be positive finite numbers ` +\n `(got warnAfterMs=${warnAfterMs}, timeoutMs=${timeoutMs})`\n );\n }\n if (warnAfterMs >= timeoutMs) {\n throw new InvalidResourceDeadlineError(\n `withResourceDeadline: warnAfterMs (${warnAfterMs}ms) must be less than timeoutMs (${timeoutMs}ms)`\n );\n }\n}\n\n/**\n * Run `operation` under a wall-clock deadline.\n *\n * Resolves with the operation's result if it settles within `timeoutMs`.\n * Rejects with the result of `opts.onTimeout(elapsedMs)` otherwise. If\n * the operation throws after the timeout has already fired, the timeout\n * error wins (we never overwrite the rejection with a late provider\n * error).\n */\nexport async function withResourceDeadline<T>(\n operation: () => Promise<T>,\n opts: ResourceDeadlineOptions\n): Promise<T> {\n validateOptions(opts);\n\n const startedAt = Date.now();\n\n return new Promise<T>((resolve, reject) => {\n let settled = false;\n let warnTimer: NodeJS.Timeout | undefined;\n let timeoutTimer: NodeJS.Timeout | undefined;\n\n const cleanup = (): void => {\n if (warnTimer !== undefined) clearTimeout(warnTimer);\n if (timeoutTimer !== undefined) clearTimeout(timeoutTimer);\n warnTimer = undefined;\n timeoutTimer = undefined;\n };\n\n if (opts.onWarn) {\n warnTimer = setTimeout(() => {\n if (settled) return;\n try {\n opts.onWarn!(Date.now() - startedAt);\n } catch {\n // onWarn is best-effort UX — never let it sink the operation.\n }\n }, opts.warnAfterMs);\n if (typeof warnTimer.unref === 'function') warnTimer.unref();\n }\n\n timeoutTimer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n const elapsed = Date.now() - startedAt;\n reject(opts.onTimeout(elapsed));\n }, opts.timeoutMs);\n if (typeof timeoutTimer.unref === 'function') timeoutTimer.unref();\n\n // Run the operation eagerly. If the timeout has already fired by the\n // time the operation settles, swallow the result silently — we have\n // already rejected the outer promise with the timeout error.\n Promise.resolve()\n .then(() => operation())\n .then(\n (value) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n },\n (err) => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(err);\n }\n );\n });\n}\n","import { getLogger } from '../utils/logger.js';\nimport { getLiveRenderer } from '../utils/live-renderer.js';\nimport { ProvisioningError, ResourceTimeoutError } from '../utils/error-handler.js';\nimport { withStackName, applyDefaultNameForFallback } from '../provisioning/resource-name.js';\nimport { IntrinsicFunctionResolver } from './intrinsic-function-resolver.js';\nimport { DagExecutor } from './dag-executor.js';\nimport type { CloudFormationTemplate, ResourceProvider } from '../types/resource.js';\nimport {\n STATE_SCHEMA_VERSION_CURRENT,\n shouldRetainResource,\n type StackState,\n type StateImportEntry,\n type ResourceState,\n type ResourceChange,\n} from '../types/state.js';\nimport type { S3StateBackend } from '../state/s3-state-backend.js';\nimport type { LockManager } from '../state/lock-manager.js';\nimport type { ExportIndexStore } from '../state/export-index-store.js';\nimport type { DagBuilder } from '../analyzer/dag-builder.js';\nimport type { DiffCalculator } from '../analyzer/diff-calculator.js';\nimport { ProviderRegistry } from '../provisioning/provider-registry.js';\nimport { CloudControlProvider } from '../provisioning/cloud-control-provider.js';\nimport { TemplateParser } from '../analyzer/template-parser.js';\nimport { IMPLICIT_DELETE_DEPENDENCIES } from '../analyzer/implicit-delete-deps.js';\nimport { withRetry } from './retry.js';\nimport { withResourceDeadline } from './resource-deadline.js';\n\n/**\n * Completed operation record for rollback tracking\n */\ninterface CompletedOperation {\n /** Logical ID of the resource */\n logicalId: string;\n /** Type of change that was applied */\n changeType: 'CREATE' | 'UPDATE' | 'DELETE';\n /** Resource type (e.g., \"AWS::S3::Bucket\") */\n resourceType: string;\n /** Previous resource state (for UPDATE rollback) */\n previousState?: ResourceState | undefined;\n /** Physical ID of newly created resource (for CREATE rollback) */\n physicalId?: string | undefined;\n /** Properties used for creation (for CREATE rollback / delete) */\n properties?: Record<string, unknown> | undefined;\n}\n\n/**\n * Default per-resource warn threshold: warn the user when a single\n * resource has been in flight for 5 minutes. Most CC API resources\n * complete in under a minute; 5m is the agreed elbow.\n */\nexport const DEFAULT_RESOURCE_WARN_AFTER_MS = 5 * 60 * 1000;\n\n/**\n * Default per-resource hard timeout: abort after 30 minutes. Matches the\n * design doc — Custom-Resource-heavy stacks should pass `--resource-timeout 1h`\n * explicitly because the Custom Resource provider's polling cap is 1h.\n */\nexport const DEFAULT_RESOURCE_TIMEOUT_MS = 30 * 60 * 1000;\n\n/**\n * Deploy engine options\n */\nexport interface DeployEngineOptions {\n /** Maximum concurrent resource operations */\n concurrency?: number;\n /** Dry run mode (plan only, no actual changes) */\n dryRun?: boolean;\n /** Lock timeout in milliseconds */\n lockTimeout?: number;\n /** User-provided parameter values */\n parameters?: Record<string, string>;\n /** Skip rollback on failure (save partial state and fail) */\n noRollback?: boolean;\n /**\n * Per-resource warn threshold (ms). When a single CREATE / UPDATE /\n * DELETE has been running this long, the live renderer's task label\n * gets a \"[taking longer than expected, Nm+]\" suffix and a\n * `logger.warn` line is emitted. Defaults to\n * {@link DEFAULT_RESOURCE_WARN_AFTER_MS}.\n *\n * Per-type override via {@link resourceWarnAfterByType} wins for\n * matching resource types.\n */\n resourceWarnAfterMs?: number;\n /**\n * Per-resource hard timeout (ms). When a single resource exceeds this,\n * `ResourceTimeoutError` is thrown and the existing rollback path\n * runs. Defaults to {@link DEFAULT_RESOURCE_TIMEOUT_MS}.\n *\n * Per-type override via {@link resourceTimeoutByType} wins for\n * matching resource types.\n */\n resourceTimeoutMs?: number;\n /**\n * Per-resource-type warn-after override map. Keys are\n * `AWS::Service::Resource` strings; values are milliseconds. When the\n * resource being provisioned matches a key here, that value supersedes\n * `resourceWarnAfterMs` at the call site.\n */\n resourceWarnAfterByType?: Record<string, number>;\n /**\n * Per-resource-type hard-timeout override map. Same shape as\n * {@link resourceWarnAfterByType}; supersedes `resourceTimeoutMs` at\n * the call site for matching types.\n */\n resourceTimeoutByType?: Record<string, number>;\n /**\n * When true, kick off `provider.readCurrentState` immediately after\n * each successful create / update so its result lands in\n * `ResourceState.observedProperties` for the drift comparator. Calls\n * are fire-and-forget — the deploy critical path does NOT block on\n * them — and a final `Promise.all` drains the in-flight set right\n * before the success state save.\n *\n * Defaults to `true`. Pass `--no-capture-observed-state` (or set\n * `cdk.json context.cdkd.captureObservedState: false`) to disable\n * when deploy speed is more important than rich drift detection.\n */\n captureObservedState?: boolean;\n}\n\n/**\n * Deploy result\n */\nexport interface DeployResult {\n /** Stack name */\n stackName: string;\n /** Number of resources created */\n created: number;\n /** Number of resources updated */\n updated: number;\n /** Number of resources deleted */\n deleted: number;\n /** Number of resources unchanged */\n unchanged: number;\n /** Total deployment time in milliseconds */\n durationMs: number;\n}\n\n/**\n * Deploy engine orchestrates the entire deployment process\n *\n * Responsibilities:\n * 1. Acquire stack lock\n * 2. Load current state\n * 3. Calculate diff\n * 4. Validate resource types\n * 5. Execute deployment in DAG order\n * 6. Save new state\n * 7. Release lock\n *\n * Rollback mechanism:\n * - Tracks completed operations during deployment\n * - On failure, rolls back in reverse order (best-effort)\n * - Supports --no-rollback flag to skip rollback (saves partial state and fails)\n * - CREATE → delete the newly created resource\n * - UPDATE → restore previous properties\n * - DELETE → cannot rollback (log warning)\n */\n/**\n * Error thrown when deployment is interrupted by SIGINT\n */\nclass InterruptedError extends Error {\n constructor() {\n super('Deployment interrupted by user (Ctrl+C)');\n this.name = 'InterruptedError';\n }\n}\n\nexport class DeployEngine {\n private logger = getLogger().child('DeployEngine');\n private resolver: IntrinsicFunctionResolver;\n private interrupted = false;\n\n /**\n * In-flight `provider.readCurrentState` promises kicked off after a\n * successful CREATE / UPDATE. The deploy critical path does NOT\n * `await` these; instead they're drained at the end of `doDeploy`\n * (success path only) and the resolved values are merged into\n * `ResourceState.observedProperties` before the final state save.\n *\n * Each Promise resolves to the AWS-current snapshot, or `undefined`\n * if the provider does not implement `readCurrentState` or the call\n * threw — never rejects, so an unhandled-rejection cannot escape.\n */\n private observedCaptureTasks: Map<string, Promise<Record<string, unknown> | undefined>> =\n new Map();\n private stateBackend: S3StateBackend;\n private lockManager: LockManager;\n private dagBuilder: DagBuilder;\n private diffCalculator: DiffCalculator;\n private providerRegistry: ProviderRegistry;\n private options: DeployEngineOptions;\n /**\n * Optional persistent exports index store. When supplied, all\n * `Fn::ImportValue` resolutions in this deploy session prefer the\n * O(1) index lookup over the per-stack state.json scan, and the\n * consumer's `state.imports` field is populated for destroy-time\n * strong-reference checks. Shared across DeployEngine instances in\n * a single `cdkd deploy --all` invocation so the in-memory cache\n * survives across stacks.\n */\n private exportIndexStore: ExportIndexStore | undefined;\n /**\n * Per-deploy-session bag the resolver pushes resolved\n * `Fn::ImportValue` entries into. Reset at the start of each\n * `deploy()` call and persisted to `newState.imports` at the end.\n */\n private recordedImports: StateImportEntry[] = [];\n\n /**\n * Target region for this stack. Required — load-bearing for the\n * region-prefixed S3 state key and recorded in state.json for\n * cross-region destroy.\n */\n private stackRegion: string;\n\n constructor(\n stateBackend: S3StateBackend,\n lockManager: LockManager,\n dagBuilder: DagBuilder,\n diffCalculator: DiffCalculator,\n providerRegistry: ProviderRegistry,\n options: DeployEngineOptions = {},\n stackRegion: string,\n exportIndexStore?: ExportIndexStore\n ) {\n this.stateBackend = stateBackend;\n this.lockManager = lockManager;\n this.dagBuilder = dagBuilder;\n this.diffCalculator = diffCalculator;\n this.providerRegistry = providerRegistry;\n this.options = options;\n this.stackRegion = stackRegion;\n this.exportIndexStore = exportIndexStore;\n this.resolver = new IntrinsicFunctionResolver(stackRegion);\n this.options.concurrency = options.concurrency ?? 10;\n this.options.dryRun = options.dryRun ?? false;\n this.options.lockTimeout = options.lockTimeout ?? 5 * 60 * 1000; // 5 minutes\n this.options.noRollback = options.noRollback ?? false;\n this.options.resourceWarnAfterMs =\n options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;\n this.options.resourceTimeoutMs = options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;\n // Default ON: drift detection without observedProperties is the\n // pre-PR behavior and we want the upgrade to be a strict superset.\n // The opt-out exists for users who care more about deploy speed\n // than the +0-10% drift-baseline overhead.\n this.options.captureObservedState = options.captureObservedState ?? true;\n }\n\n /**\n * Deploy a CloudFormation template\n */\n async deploy(stackName: string, template: CloudFormationTemplate): Promise<DeployResult> {\n // Reset per-session state. `recordedImports` is the bag the\n // resolver pushes Fn::ImportValue resolutions into; it lands in\n // `state.imports` at deploy save time.\n this.recordedImports = [];\n // Scope `stackName` to this deploy's async chain so concurrent\n // deploys (--stack-concurrency > 1) don't see each other's value.\n // See `src/provisioning/resource-name.ts` for the AsyncLocalStorage\n // background.\n return withStackName(stackName, () => this.doDeploy(stackName, template));\n }\n\n /**\n * Resolver context with the imports-recording and exports-index\n * fields wired in. Keeps the four+ inline context construction\n * sites consistent — pass through callable as\n * `this.buildResolverContext({...}, stackName)`.\n */\n private buildResolverContext(\n base: {\n template: CloudFormationTemplate;\n resources: Record<string, ResourceState>;\n parameters?: Record<string, unknown>;\n conditions?: Record<string, boolean>;\n },\n stackName: string\n ): import('./intrinsic-function-resolver.js').ResolverContext {\n return {\n template: base.template,\n resources: base.resources,\n ...(base.parameters &&\n Object.keys(base.parameters).length > 0 && { parameters: base.parameters }),\n ...(base.conditions &&\n Object.keys(base.conditions).length > 0 && { conditions: base.conditions }),\n stateBackend: this.stateBackend,\n stackName,\n ...(this.exportIndexStore && { exportIndex: this.exportIndexStore }),\n recordedImports: this.recordedImports,\n };\n }\n\n /**\n * Kick off `provider.readCurrentState` for a freshly-created/updated\n * resource without blocking the deploy critical path. The promise\n * lands in `observedCaptureTasks` keyed by `logicalId`; the deploy's\n * success-path drain (`drainObservedCaptures`) awaits the full set\n * and merges the resolved values into `ResourceState.observedProperties`\n * before the final state save.\n *\n * Errors are swallowed at the Promise level — readCurrentState\n * failing must not fail the deploy. The map entry resolves to\n * `undefined` for failures and for providers without\n * `readCurrentState`; both translate to \"no observedProperties\" at\n * the merge step, which is fine: drift falls back to comparing\n * against `properties`.\n */\n private kickOffObservedCapture(\n provider: ResourceProvider,\n logicalId: string,\n physicalId: string,\n resourceType: string,\n resolvedProps: Record<string, unknown>,\n context?: import('../types/resource.js').ReadCurrentStateContext\n ): void {\n if (this.options.captureObservedState !== true) return;\n if (!provider.readCurrentState) return;\n\n const promise = provider\n .readCurrentState(physicalId, logicalId, resourceType, resolvedProps, context)\n .catch((err: unknown) => {\n this.logger.debug(\n `observedProperties capture for ${logicalId} (${resourceType}) failed: ${err instanceof Error ? err.message : String(err)} — drift will fall back to template properties for this resource until the next successful deploy.`\n );\n return undefined;\n });\n this.observedCaptureTasks.set(logicalId, promise);\n }\n\n /**\n * Wait for every in-flight `readCurrentState` promise from the\n * deploy's success path, then merge each resolved snapshot into the\n * matching `ResourceState.observedProperties`. After this runs the\n * map is drained so a subsequent deploy starts fresh.\n *\n * Called from `doDeploy` immediately before the final `saveState`.\n * The rollback / failure paths intentionally do NOT call this — a\n * failed deploy's partial state is already inconsistent, and waiting\n * on potentially many in-flight reads would slow down the rollback\n * itself.\n */\n private async drainObservedCaptures(\n stateResources: Record<string, ResourceState>\n ): Promise<void> {\n if (this.observedCaptureTasks.size === 0) return;\n const entries = Array.from(this.observedCaptureTasks.entries());\n this.observedCaptureTasks.clear();\n const resolved = await Promise.all(entries.map(([, p]) => p));\n for (let i = 0; i < entries.length; i++) {\n const logicalId = entries[i]![0];\n const observed = resolved[i];\n const target = stateResources[logicalId];\n if (target && observed !== undefined) {\n target.observedProperties = observed;\n }\n }\n }\n\n /**\n * Kick off `provider.readCurrentState` for every resource in the\n * loaded state that lacks `observedProperties` (e.g. state written\n * by a pre-v3 binary, or a v3 record where a NO_CHANGE-skipped\n * resource's baseline never landed). Calls go through\n * `kickOffObservedCapture`, so they share the same fire-and-forget\n * pipeline, error swallowing, and final-drain wiring that the\n * post-CREATE / post-UPDATE captures use.\n *\n * The deploy critical path does NOT wait on these; the cost is\n * bounded by `max(per-resource readCurrentState latency)` (typically\n * ~200-300ms in practice) once at the end-of-deploy drain. Any\n * resource that subsequently goes through CREATE / UPDATE in the\n * same deploy will overwrite this entry via the `Map.set` keyed by\n * `logicalId` (latest-wins) — so there's no double-write to state,\n * just a wasted SDK call for the (rare) UPDATE / DELETE intersection.\n *\n * Resources whose provider lookup throws (e.g. unsupported type) or\n * lacks `readCurrentState` are silently skipped — same policy as the\n * manual `cdkd state refresh-observed` command.\n */\n private kickOffAutoRefreshObservedProperties(\n stateResources: Record<string, ResourceState>\n ): void {\n if (this.options.captureObservedState !== true) return;\n // Dry run must not fire real SDK reads (matches the dry-run\n // guarantee that no AWS side-effect runs).\n if (this.options.dryRun === true) return;\n let toRefresh = 0;\n const candidates: Array<{\n logicalId: string;\n resource: ResourceState;\n }> = [];\n for (const [logicalId, resource] of Object.entries(stateResources)) {\n if (resource.observedProperties !== undefined) continue;\n candidates.push({ logicalId, resource });\n }\n if (candidates.length === 0) return;\n\n // Issue #323: at the v2→v3 schema-upgrade refresh path, state is\n // fully loaded from the previous deploy — sibling AWS::IAM::Policy\n // resources are all present. Pass a cross-resource context so IAM\n // providers can filter inline policies managed via sibling\n // resources, otherwise observed.Policies would record the\n // sibling-managed entries and the next `cdkd drift` would fire\n // false drift (filtered AWS-current = []) until `cdkd drift\n // --accept` runs. Build the siblings map once and clone-minus-self\n // per resource to avoid an O(N²) walk.\n const allSiblings: Record<\n string,\n { resourceType: string; properties: Record<string, unknown> }\n > = {};\n for (const [lid, res] of Object.entries(stateResources)) {\n allSiblings[lid] = {\n resourceType: res.resourceType,\n properties: res.properties ?? {},\n };\n }\n\n for (const { logicalId, resource } of candidates) {\n // Skip-list / unsupported types: getProvider throws — silently skip\n // (mirrors `cdkd state refresh-observed`'s policy: best-effort,\n // no failure on a state record we cannot resolve).\n let provider: ResourceProvider;\n try {\n provider = this.providerRegistry.getProvider(resource.resourceType);\n } catch {\n continue;\n }\n if (!provider.readCurrentState) continue;\n const siblings = { ...allSiblings };\n delete siblings[logicalId];\n this.kickOffObservedCapture(\n provider,\n logicalId,\n resource.physicalId,\n resource.resourceType,\n resource.properties ?? {},\n { siblings }\n );\n toRefresh++;\n }\n\n if (toRefresh > 0) {\n this.logger.warn(\n `cdkd state schema upgrade detected — refreshing observed-properties baseline for ${toRefresh} resource(s) (one-time, runs in parallel with deploy)`\n );\n }\n }\n\n private async doDeploy(\n stackName: string,\n template: CloudFormationTemplate\n ): Promise<DeployResult> {\n const startTime = Date.now();\n this.logger.debug(`Starting deployment for stack: ${stackName}`);\n\n // Acquire lock with retry (retries up to 3 times with 2s delay for transient lock conflicts)\n await this.lockManager.acquireLockWithRetry(stackName, this.stackRegion, undefined, 'deploy');\n\n // Live progress renderer: shows in-flight resources as a multi-line area\n // at the bottom of the terminal. Self-disables on non-TTY and when\n // `CDKD_NO_LIVE=1` is set (the CLI sets this in verbose mode so debug\n // logs do not interleave with the live area).\n const renderer = getLiveRenderer();\n renderer.start();\n\n // Register SIGINT handler to save partial state on Ctrl+C\n this.interrupted = false;\n const sigintHandler = () => {\n // Route the interrupt notice through the live renderer so it does not\n // collide with the in-flight task display.\n renderer.printAbove(() => {\n process.stderr.write(\n '\\nInterrupted — saving partial state after current operations complete...\\n'\n );\n });\n this.interrupted = true;\n };\n process.on('SIGINT', sigintHandler);\n\n try {\n // 1. Load current state\n const currentStateData = await this.stateBackend.getState(stackName, this.stackRegion);\n const currentState: StackState = currentStateData?.state ?? {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName,\n resources: {},\n outputs: {},\n lastModified: Date.now(),\n };\n const currentEtag = currentStateData?.etag;\n // Set when we loaded a `version: 1` legacy record. The next save\n // migrates it to the new key.\n const migrationPending = currentStateData?.migrationPending ?? false;\n\n this.logger.debug(\n `Loaded current state: ${Object.keys(currentState.resources).length} resources`\n );\n\n // 1a. Auto-refresh observedProperties for any state entry that lacks it\n // (state written by an older binary / direct edit). Fires\n // `provider.readCurrentState` fire-and-forget through the same\n // `kickOffObservedCapture` pipeline that successful CREATE / UPDATE\n // uses, so the in-flight set is drained right before the final\n // `saveState`. Latest-wins semantics (Map.set keyed by logicalId)\n // means a CREATE / UPDATE later in the same deploy overwrites\n // the auto-refresh entry — no double-write to state. CREATEs for\n // brand-new resources skip this loop because they're not yet in\n // `currentState.resources`. Closes the upgrade UX gap left by\n // v3 schema: the manual `cdkd state refresh-observed` command\n // remains for non-deploy refresh.\n this.kickOffAutoRefreshObservedProperties(currentState.resources);\n\n // 2. Template parsing is handled by DagBuilder (dependency analysis) and\n // IntrinsicResolver (intrinsic function resolution) in later steps\n this.logger.debug(`Template has ${Object.keys(template.Resources || {}).length} resources`);\n\n // 2.5. Resolve parameters from template and user input\n const parameterValues = await this.resolver.resolveParameters(\n template,\n this.options.parameters\n );\n this.logger.debug(\n `Resolved ${Object.keys(parameterValues).length} parameters: ${Object.keys(parameterValues).join(', ')}`\n );\n\n // 2.6. Evaluate conditions from template\n const context = this.buildResolverContext(\n {\n template,\n resources: currentState.resources,\n parameters: parameterValues,\n },\n stackName\n );\n const conditions = await this.resolver.evaluateConditions(context);\n this.logger.debug(\n `Evaluated ${Object.keys(conditions).length} conditions: ${Object.keys(conditions).join(', ')}`\n );\n\n // 3. Validate resource types (before deployment starts)\n // Skip metadata resources as they don't actually deploy\n const resourceTypes = new Set(\n Object.values(template.Resources || {})\n .map((r) => r.Type)\n .filter((type) => type !== 'AWS::CDK::Metadata')\n );\n this.providerRegistry.validateResourceTypes(resourceTypes);\n this.logger.debug(`All resource types validated`);\n\n // 4. Build dependency graph\n const dag = this.dagBuilder.buildGraph(template);\n const executionLevels = this.dagBuilder.getExecutionLevels(dag);\n this.logger.debug(`Dependency graph: ${executionLevels.length} execution levels`);\n\n // 5. Calculate diff\n // Pass a best-effort resolver so that changes hidden inside intrinsics (e.g.\n // `Fn::Join` literal args like \"-value\" -> \"-value2\") are detected against\n // the already-resolved values stored in state.\n const diffResolverContext = this.buildResolverContext(\n {\n template,\n resources: currentState.resources,\n parameters: parameterValues,\n conditions,\n },\n stackName\n );\n const diffResolveFn = (value: unknown) => this.resolver.resolve(value, diffResolverContext);\n const changes = await this.diffCalculator.calculateDiff(\n currentState,\n template,\n diffResolveFn\n );\n const hasChanges = this.diffCalculator.hasChanges(changes);\n\n if (!hasChanges) {\n this.logger.info('No changes detected. Stack is up to date.');\n\n // No-change path: if the auto-refresh kicked off any\n // readCurrentState calls (e.g. v2 → v3 schema upgrade on a\n // stack with nothing to deploy), drain them and persist the\n // refreshed observed-properties baseline so the next `cdkd\n // drift` run sees a real AWS-current snapshot. Skipped in\n // dry-run / when nothing was kicked off (drainObservedCaptures\n // short-circuits on empty map).\n if (!this.options.dryRun && this.observedCaptureTasks.size > 0) {\n await this.drainObservedCaptures(currentState.resources);\n try {\n const refreshedState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: currentState.resources,\n outputs: currentState.outputs,\n // Preserve existing imports[] (no-change path: nothing\n // re-resolved). Otherwise the refresh would silently\n // strip the strong-reference record on every diff-clean\n // deploy.\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n const saveOptions: { expectedEtag?: string; migrateLegacy?: boolean } = {};\n if (currentEtag !== undefined) saveOptions.expectedEtag = currentEtag;\n if (migrationPending) saveOptions.migrateLegacy = true;\n await this.stateBackend.saveState(\n stackName,\n this.stackRegion,\n refreshedState,\n saveOptions\n );\n this.logger.debug('Persisted refreshed observedProperties (no-change path)');\n } catch (saveError) {\n this.logger.warn(\n `Failed to persist refreshed observedProperties: ${saveError instanceof Error ? saveError.message : String(saveError)} — drift baseline will be re-fetched on next deploy.`\n );\n }\n }\n\n return {\n stackName,\n created: 0,\n updated: 0,\n deleted: 0,\n unchanged: Object.keys(currentState.resources).length,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Log changes summary\n const createChanges = this.diffCalculator.filterByType(changes, 'CREATE');\n const updateChanges = this.diffCalculator.filterByType(changes, 'UPDATE');\n const deleteChanges = this.diffCalculator.filterByType(changes, 'DELETE');\n\n this.logger.info(\n `Changes: ${createChanges.length} to create, ${updateChanges.length} to update, ${deleteChanges.length} to delete`\n );\n\n if (this.options.dryRun) {\n this.logger.info('Dry run mode - skipping actual deployment');\n return {\n stackName,\n created: createChanges.length,\n updated: updateChanges.length,\n deleted: deleteChanges.length,\n unchanged: this.diffCalculator.filterByType(changes, 'NO_CHANGE').length,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Progress counter for tracking overall deployment progress\n const totalOperations = createChanges.length + updateChanges.length + deleteChanges.length;\n const progress = { current: 0, total: totalOperations };\n\n // 6. Execute deployment (event-driven DAG dispatch with partial state saves)\n const { state: newState, actualCounts } = await this.executeDeployment(\n template,\n currentState,\n changes,\n dag,\n executionLevels,\n stackName,\n parameterValues,\n conditions,\n currentEtag,\n progress,\n migrationPending\n );\n\n // 7a. Drain in-flight readCurrentState promises so each resource's\n // observedProperties lands in newState before we persist it. By\n // this point the deploy critical path is over, so awaiting the\n // remaining captures only adds the longest still-pending read\n // (typically <300ms in practice for medium stacks; see PR notes).\n await this.drainObservedCaptures(newState.resources);\n\n // 7b. Save final state (ETag may have been updated by partial saves).\n // The legacy migration delete (when migrationPending) was already done by\n // the first per-resource save inside executeDeployment, so this final\n // save is unconditionally region-scoped.\n const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, newState);\n this.logger.debug(`State saved (ETag: ${newEtag})`);\n\n // 7c. Update the persistent exports index with this stack's\n // outputs so subsequent `Fn::ImportValue` resolves hit O(1).\n // Best-effort: failures are swallowed inside updateForStack and\n // surfaced as warnings (state.json is canonical; a stale index\n // self-heals on the next deploy/resolve fallback).\n if (this.exportIndexStore) {\n await this.exportIndexStore.updateForStack(\n stackName,\n this.stackRegion,\n (newState.outputs as Record<string, unknown>) ?? {}\n );\n }\n\n const durationMs = Date.now() - startTime;\n const unchangedCount =\n this.diffCalculator.filterByType(changes, 'NO_CHANGE').length + actualCounts.skipped;\n\n return {\n stackName,\n created: actualCounts.created,\n updated: actualCounts.updated,\n deleted: actualCounts.deleted,\n unchanged: unchangedCount,\n durationMs,\n };\n } finally {\n // Stop live renderer (clears any remaining in-flight task display)\n renderer.stop();\n\n // Remove SIGINT handler\n process.removeListener('SIGINT', sigintHandler);\n\n // On a rollback / SIGINT exit we may leave in-flight readCurrentState\n // promises in the map (the success path drains them above). Clear the\n // map so a re-used engine instance does not accumulate stale entries\n // across deploys. The underlying promises already have a `.catch` so\n // dropping the references will not produce an unhandled rejection.\n this.observedCaptureTasks.clear();\n\n // Always release lock\n try {\n await this.lockManager.releaseLock(stackName, this.stackRegion);\n this.logger.debug('Lock released');\n } catch (lockError) {\n this.logger.warn(\n `Failed to release lock: ${lockError instanceof Error ? lockError.message : String(lockError)}`\n );\n }\n }\n }\n\n /**\n * Execute deployment by processing resources via event-driven DAG dispatch.\n *\n * - CREATE/UPDATE follow forward dependency order (a node starts as soon as\n * ALL of its dependencies are completed — does not wait for unrelated\n * siblings in the same \"level\")\n * - DELETE follows reverse dependency order (a node starts as soon as all\n * resources that depend ON it have finished deleting)\n */\n private async executeDeployment(\n template: CloudFormationTemplate,\n currentState: StackState,\n changes: Map<string, ResourceChange>,\n dag: ReturnType<DagBuilder['buildGraph']>,\n executionLevels: string[][],\n stackName: string,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>,\n currentEtag?: string,\n progress?: { current: number; total: number },\n migrationPending = false\n ): Promise<{\n state: StackState;\n actualCounts: { created: number; updated: number; deleted: number; skipped: number };\n }> {\n const concurrency = this.options.concurrency!;\n const newResources: Record<string, ResourceState> = { ...currentState.resources };\n const actualCounts = { created: 0, updated: 0, deleted: 0, skipped: 0 };\n const completedOperations: CompletedOperation[] = [];\n // Tracked here so the FIRST per-resource save sweeps the legacy key; we\n // don't want to delete it on every save.\n let pendingMigration = migrationPending;\n\n // Serialize per-resource state saves to avoid ETag conflicts from concurrent writes\n let saveChain: Promise<void> = Promise.resolve();\n const saveStateAfterResource = (logicalId: string): void => {\n if (currentEtag === undefined) return;\n saveChain = saveChain.then(async () => {\n try {\n const partialState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n // Per-resource partial save: imports[] reverts to the\n // pre-deploy snapshot. recordedImports from this session\n // are persisted only on the final success path.\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n // Migration is a one-shot tail on the first save; subsequent saves\n // overwrite the new key in-place under optimistic locking.\n const migrate = pendingMigration;\n const expectedEtag = migrate ? undefined : currentEtag;\n currentEtag = await this.stateBackend.saveState(\n stackName,\n this.stackRegion,\n partialState,\n { ...(expectedEtag !== undefined && { expectedEtag }), migrateLegacy: migrate }\n );\n if (migrate) pendingMigration = false;\n this.logger.debug(`State saved after ${logicalId}`);\n } catch (error) {\n this.logger.warn(\n `Failed to save state after ${logicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n });\n };\n\n // Separate DELETE operations from CREATE/UPDATE\n const deleteChanges = new Set(\n Array.from(changes.entries())\n .filter(([_, change]) => change.changeType === 'DELETE')\n .map(([logicalId]) => logicalId)\n );\n\n try {\n // Step 1: Process CREATE/UPDATE via event-driven DAG dispatch.\n // A node starts as soon as ALL of its dependencies are completed, rather\n // than waiting for an entire \"level\" of unrelated siblings to finish.\n const createUpdateIds: string[] = [];\n for (const [id, change] of changes.entries()) {\n if (deleteChanges.has(id)) continue;\n if (change.changeType === 'NO_CHANGE') continue;\n createUpdateIds.push(id);\n }\n\n if (createUpdateIds.length > 0) {\n this.logger.info(\n `Deploying ${createUpdateIds.length} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`\n );\n\n const createUpdateExecutor = new DagExecutor<ResourceChange>();\n const provisionable = new Set(createUpdateIds);\n for (const id of createUpdateIds) {\n const allDeps = this.dagBuilder.getDirectDependencies(dag, id);\n // Only carry deps that are themselves being provisioned in this phase;\n // NO_CHANGE / DELETE / non-DAG deps are already satisfied.\n const deps = new Set(allDeps.filter((d) => provisionable.has(d)));\n createUpdateExecutor.add({\n id,\n dependencies: deps,\n state: 'pending',\n data: changes.get(id)!,\n });\n }\n\n try {\n await createUpdateExecutor.execute(\n concurrency,\n async (node) => {\n const logicalId = node.id;\n const change = node.data;\n\n const previousState = currentState.resources[logicalId]\n ? { ...currentState.resources[logicalId] }\n : undefined;\n\n try {\n await this.provisionResource(\n logicalId,\n change,\n newResources,\n stackName,\n template,\n parameterValues,\n conditions,\n actualCounts,\n progress\n );\n } catch (provisionError) {\n // Signal interruption so that long-running operations (e.g., CloudFront\n // waitForDeployed) in sibling tasks abort promptly instead of blocking\n // until their own polling timeouts fire.\n this.interrupted = true;\n throw provisionError;\n }\n\n completedOperations.push({\n logicalId,\n changeType: change.changeType as 'CREATE' | 'UPDATE',\n resourceType: change.resourceType,\n previousState,\n physicalId: newResources[logicalId]?.physicalId,\n properties: newResources[logicalId]?.properties,\n });\n\n saveStateAfterResource(logicalId);\n },\n () => this.interrupted\n );\n } finally {\n // Wait for any pending per-resource state saves before the next phase or\n // before propagating an error — prevents partial-save races.\n await saveChain;\n }\n\n // If SIGINT fired AND there is still un-provisioned work (some nodes\n // remained pending because dispatch was cancelled), surface it as an\n // explicit interruption so the catch path saves partial state.\n // If every node already completed before SIGINT landed, treat the deploy\n // as fully successful — matches the prior level-loop's \"loop exits, no\n // check\" behaviour at the very end of execution.\n if (this.interrupted && this.hasPending(createUpdateExecutor)) {\n throw new InterruptedError();\n }\n }\n\n // Step 2: Process DELETE operations in reverse dependency order.\n if (deleteChanges.size > 0) {\n this.logger.info(`Deleting ${deleteChanges.size} resource(s)`);\n\n const deleteDeps = this.buildDeletionDependencies(deleteChanges, currentState);\n const deleteExecutor = new DagExecutor<ResourceChange>();\n for (const id of deleteChanges) {\n deleteExecutor.add({\n id,\n dependencies: deleteDeps.get(id) ?? new Set(),\n state: 'pending',\n data: changes.get(id)!,\n });\n }\n\n try {\n await deleteExecutor.execute(\n concurrency,\n async (node) => {\n const logicalId = node.id;\n const change = node.data;\n\n const previousState = currentState.resources[logicalId]\n ? { ...currentState.resources[logicalId] }\n : undefined;\n\n try {\n await this.provisionResource(\n logicalId,\n change,\n newResources,\n stackName,\n template,\n parameterValues,\n conditions,\n actualCounts,\n progress\n );\n } catch (provisionError) {\n this.interrupted = true;\n throw provisionError;\n }\n\n completedOperations.push({\n logicalId,\n changeType: 'DELETE',\n resourceType: change.resourceType,\n previousState,\n });\n\n saveStateAfterResource(logicalId);\n },\n () => this.interrupted\n );\n } finally {\n await saveChain;\n }\n\n if (this.interrupted && this.hasPending(deleteExecutor)) {\n throw new InterruptedError();\n }\n }\n } catch (error) {\n // Save partial state BEFORE rollback to track all successfully provisioned\n // resources (including those that completed concurrently with the one that\n // failed). This prevents orphaned resources — resources that exist in AWS\n // but not in the state file.\n try {\n const preRollbackState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n const migrate = pendingMigration;\n const expectedEtag = migrate ? undefined : currentEtag;\n currentEtag = await this.stateBackend.saveState(\n stackName,\n this.stackRegion,\n preRollbackState,\n { ...(expectedEtag !== undefined && { expectedEtag }), migrateLegacy: migrate }\n );\n if (migrate) pendingMigration = false;\n this.logger.debug('Partial state saved before rollback (orphaned resource tracking)');\n } catch (saveError) {\n this.logger.warn(\n `Failed to save partial state before rollback: ${saveError instanceof Error ? saveError.message : String(saveError)}`\n );\n }\n\n // On SIGINT, skip rollback — just save partial state and let the caller exit\n if (error instanceof InterruptedError) {\n this.logger.info(\n `Partial state saved (${Object.keys(newResources).length} resources). ` +\n 'Run deploy again to resume, or destroy to clean up.'\n );\n throw error;\n }\n\n // Deployment failed — attempt rollback unless --no-rollback is set\n if (this.options.noRollback) {\n this.logger.warn('Deployment failed. --no-rollback is set, skipping rollback.');\n this.logger.warn('Partial state has been saved. Manual cleanup may be required.');\n } else {\n await this.performRollback(completedOperations, newResources, stackName);\n }\n\n // Save state after rollback (reflects rolled-back resource state).\n // This is critical: if rollback deleted resources, the state must reflect\n // that. Otherwise, next deploy will think deleted resources still exist.\n try {\n const postRollbackState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n await this.stateBackend.saveState(stackName, this.stackRegion, postRollbackState, {\n ...(currentEtag !== undefined && { expectedEtag: currentEtag }),\n });\n this.logger.debug('State saved after deployment failure');\n } catch (saveError) {\n // ETag mismatch from per-resource saves — force overwrite with fresh ETag\n this.logger.debug(\n `Retrying state save after rollback (ETag mismatch): ${saveError instanceof Error ? saveError.message : String(saveError)}`\n );\n try {\n const freshState = await this.stateBackend.getState(stackName, this.stackRegion);\n const freshEtag = freshState?.etag;\n const postRollbackState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n await this.stateBackend.saveState(stackName, this.stackRegion, postRollbackState, {\n ...(freshEtag !== undefined && { expectedEtag: freshEtag }),\n });\n this.logger.debug('State saved after deployment failure (retry succeeded)');\n } catch (retryError) {\n this.logger.warn(\n `Failed to save state after rollback: ${retryError instanceof Error ? retryError.message : String(retryError)}`\n );\n }\n }\n\n throw error;\n }\n\n // Resolve outputs\n const outputs = await this.resolveOutputs(\n template,\n newResources,\n stackName,\n parameterValues,\n conditions\n );\n\n return {\n state: {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs,\n ...(this.recordedImports.length > 0 && { imports: [...this.recordedImports] }),\n lastModified: Date.now(),\n },\n actualCounts,\n };\n }\n\n /**\n * Perform best-effort rollback of completed operations respecting dependencies\n *\n * - CREATE → delete the newly created resource (in reverse dependency order)\n * - UPDATE → update back to previous properties\n * - DELETE → cannot rollback (resource already deleted), log warning\n *\n * Resources completed concurrently in the dispatcher may have dependencies\n * between them (e.g., IAM Policy depends on IAM Role). When rolling back\n * CREATEs (deleting), dependent resources must be deleted before their\n * dependencies. This method sorts CREATE rollback operations using dependency\n * information from state, then processes UPDATE/DELETE rollbacks, and finally\n * processes sorted CREATE rollback deletions.\n */\n private async performRollback(\n completedOperations: CompletedOperation[],\n stateResources: Record<string, ResourceState>,\n _stackName: string\n ): Promise<void> {\n if (completedOperations.length === 0) {\n this.logger.info('No completed operations to roll back.');\n return;\n }\n\n this.logger.info(`Rolling back ${completedOperations.length} completed operation(s)...`);\n\n // Separate CREATE operations (which need dependency-aware ordering) from others\n const createOps: CompletedOperation[] = [];\n const otherOps: CompletedOperation[] = [];\n\n for (const op of completedOperations) {\n if (op.changeType === 'CREATE') {\n createOps.push(op);\n } else {\n otherOps.push(op);\n }\n }\n\n // Step 1: Process UPDATE/DELETE rollbacks in reverse order (simple reversal is fine)\n for (let i = otherOps.length - 1; i >= 0; i--) {\n const op = otherOps[i]!;\n await this.performSingleRollback(op, stateResources);\n }\n\n // Step 2: Process CREATE rollbacks (deletions) in dependency-aware order\n // (reverse dependency: dependents are deleted before their dependencies)\n if (createOps.length > 0) {\n const sortedCreateOps = this.sortRollbackCreates(createOps, stateResources);\n for (const op of sortedCreateOps) {\n await this.performSingleRollback(op, stateResources);\n }\n }\n\n this.logger.info('Rollback completed. Some resources may remain if deletion failed.');\n }\n\n /**\n * Sort CREATE rollback operations so that resources depending on others\n * are deleted first (reverse dependency order).\n *\n * Uses state dependencies to determine reverse-dependency order, similar to buildDeletionDependencies.\n */\n private sortRollbackCreates(\n createOps: CompletedOperation[],\n stateResources: Record<string, ResourceState>\n ): CompletedOperation[] {\n const opMap = new Map<string, CompletedOperation>();\n const deleteIds = new Set<string>();\n for (const op of createOps) {\n opMap.set(op.logicalId, op);\n deleteIds.add(op.logicalId);\n }\n\n // Build reverse dependency map: resource → resources that depend on it\n const dependedBy = new Map<string, Set<string>>();\n for (const id of deleteIds) {\n if (!dependedBy.has(id)) dependedBy.set(id, new Set());\n }\n\n for (const id of deleteIds) {\n const resource = stateResources[id];\n if (!resource?.dependencies) continue;\n for (const dep of resource.dependencies) {\n if (!deleteIds.has(dep)) continue;\n // id depends on dep → dep must be deleted AFTER id\n if (!dependedBy.has(dep)) dependedBy.set(dep, new Set());\n dependedBy.get(dep)!.add(id);\n }\n }\n\n // Topological sort (Kahn's algorithm) — produces levels for parallel delete\n const sorted: CompletedOperation[] = [];\n let remaining = new Set(deleteIds);\n\n while (remaining.size > 0) {\n // Find resources with no remaining dependents (safe to delete now)\n const level: string[] = [];\n for (const id of remaining) {\n const dependents = dependedBy.get(id);\n const hasPendingDependents = dependents\n ? [...dependents].some((d) => remaining.has(d))\n : false;\n if (!hasPendingDependents) {\n level.push(id);\n }\n }\n\n if (level.length === 0) {\n // Circular dependency fallback: add all remaining\n this.logger.warn(\n `Circular dependency detected in rollback order, processing remaining ${remaining.size} resources`\n );\n for (const id of remaining) {\n const op = opMap.get(id);\n if (op) sorted.push(op);\n }\n break;\n }\n\n for (const id of level) {\n const op = opMap.get(id);\n if (op) sorted.push(op);\n }\n remaining = new Set([...remaining].filter((id) => !level.includes(id)));\n }\n\n this.logger.debug(\n `Rollback CREATE deletion order: ${sorted.map((op) => op.logicalId).join(' → ')}`\n );\n return sorted;\n }\n\n /**\n * Perform a single rollback operation (extracted for reuse)\n */\n private async performSingleRollback(\n op: CompletedOperation,\n stateResources: Record<string, ResourceState>\n ): Promise<void> {\n try {\n switch (op.changeType) {\n case 'CREATE': {\n // Rollback CREATE by deleting the newly created resource\n if (!op.physicalId) {\n this.logger.warn(` Rollback: Cannot delete ${op.logicalId} — no physical ID recorded`);\n break;\n }\n\n this.logger.info(\n ` Rollback: Deleting created resource ${op.logicalId} (${op.resourceType})`\n );\n const provider = this.providerRegistry.getProvider(op.resourceType);\n await provider.delete(op.logicalId, op.physicalId, op.resourceType, op.properties, {\n expectedRegion: this.stackRegion,\n });\n\n // Remove from state\n delete stateResources[op.logicalId];\n this.logger.info(` Rollback: ${op.logicalId} deleted successfully`);\n break;\n }\n\n case 'UPDATE': {\n // Rollback UPDATE by restoring previous properties\n if (!op.previousState) {\n this.logger.warn(\n ` Rollback: Cannot restore ${op.logicalId} — no previous state available`\n );\n break;\n }\n\n this.logger.info(\n ` Rollback: Restoring ${op.logicalId} (${op.resourceType}) to previous state`\n );\n const provider = this.providerRegistry.getProvider(op.resourceType);\n const currentResource = stateResources[op.logicalId];\n\n if (!currentResource) {\n this.logger.warn(\n ` Rollback: Cannot restore ${op.logicalId} — resource not found in current state`\n );\n break;\n }\n\n await provider.update(\n op.logicalId,\n currentResource.physicalId,\n op.resourceType,\n op.previousState.properties,\n currentResource.properties\n );\n\n // Restore previous state\n stateResources[op.logicalId] = op.previousState;\n this.logger.info(` Rollback: ${op.logicalId} restored successfully`);\n break;\n }\n\n case 'DELETE': {\n // Cannot rollback DELETE — resource is already deleted\n this.logger.warn(\n ` Rollback: Cannot restore deleted resource ${op.logicalId} (${op.resourceType}) — resource has already been deleted`\n );\n break;\n }\n }\n } catch (rollbackError) {\n // Best-effort: log warning and continue with remaining rollbacks\n this.logger.warn(\n ` Rollback failed for ${op.logicalId} (${op.changeType}): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n );\n this.logger.warn(' Continuing with remaining rollback operations...');\n }\n }\n\n /**\n * Provision a single resource (CREATE/UPDATE/DELETE)\n */\n private async provisionResource(\n logicalId: string,\n change: ResourceChange,\n stateResources: Record<string, ResourceState>,\n stackName: string,\n template?: CloudFormationTemplate,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>,\n counts?: { created: number; updated: number; deleted: number; skipped: number },\n progress?: { current: number; total: number }\n ): Promise<void> {\n const resourceType = change.resourceType;\n\n const renderer = getLiveRenderer();\n const needsReplacement =\n change.changeType === 'UPDATE' &&\n (change.propertyChanges?.some((pc) => pc.requiresReplacement) ?? false);\n const verb =\n change.changeType === 'CREATE'\n ? 'Creating'\n : change.changeType === 'DELETE'\n ? 'Deleting'\n : needsReplacement\n ? 'Replacing'\n : 'Updating';\n const baseLabel = `${verb} ${logicalId} (${resourceType})`;\n renderer.addTask(logicalId, baseLabel);\n\n // Operation classification for the timeout error message. UPDATE and\n // its replacement-replacement form are both surfaced as 'UPDATE' since\n // the user-facing distinction (which immutable property triggered it)\n // is already in the renderer label.\n const operationKind: 'CREATE' | 'UPDATE' | 'DELETE' =\n change.changeType === 'CREATE'\n ? 'CREATE'\n : change.changeType === 'DELETE'\n ? 'DELETE'\n : 'UPDATE';\n\n // Per-resource-type overrides (v2) win over the global default.\n // Resolution order at the call site:\n // 1. per-type CLI override map for this resourceType — explicit\n // escape hatch, always wins (`--resource-timeout TYPE=DURATION`).\n // 2. provider self-report (`getMinResourceTimeoutMs()`) raised\n // against the global default — long-running providers\n // (Custom Resource polls up to 1h) lift the deadline for their\n // resources without forcing every user to remember\n // `--resource-timeout 1h`.\n // 3. CLI global default (`--resource-timeout 30m`).\n // 4. compile-time default (DEFAULT_RESOURCE_*_MS).\n const provider = this.providerRegistry.getProvider(resourceType);\n const providerMinTimeoutMs = provider.getMinResourceTimeoutMs?.() ?? 0;\n const warnAfterMs =\n this.options.resourceWarnAfterByType?.[resourceType] ??\n this.options.resourceWarnAfterMs ??\n DEFAULT_RESOURCE_WARN_AFTER_MS;\n const globalTimeoutMs = this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;\n const timeoutMs =\n this.options.resourceTimeoutByType?.[resourceType] ??\n Math.max(providerMinTimeoutMs, globalTimeoutMs);\n\n try {\n await withResourceDeadline(\n async () => {\n await this.provisionResourceBody(\n logicalId,\n change,\n stateResources,\n stackName,\n template,\n parameterValues,\n conditions,\n counts,\n progress\n );\n },\n {\n warnAfterMs,\n timeoutMs,\n onWarn: (elapsedMs) => {\n const minutes = Math.max(1, Math.round(elapsedMs / 60_000));\n const warnSuffix = ` [taking longer than expected, ${minutes}m+]`;\n // Mutate the live renderer's task label in place (TTY mode)\n // and emit a warn line above the live area (non-TTY / verbose).\n renderer.updateTaskLabel(logicalId, `${baseLabel}${warnSuffix}`);\n renderer.printAbove(() => {\n this.logger.warn(\n `${logicalId} (${resourceType}) has been ${operationKind === 'CREATE' ? 'creating' : operationKind === 'DELETE' ? 'deleting' : 'updating'} for ${minutes}m — still waiting`\n );\n });\n },\n onTimeout: (elapsedMs) =>\n new ResourceTimeoutError(\n logicalId,\n resourceType,\n this.stackRegion,\n elapsedMs,\n operationKind,\n timeoutMs\n ),\n }\n );\n } catch (error) {\n renderer.removeTask(logicalId);\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);\n\n throw new ProvisioningError(\n `Failed to ${change.changeType.toLowerCase()} resource ${logicalId}`,\n resourceType,\n logicalId,\n stateResources[logicalId]?.physicalId,\n error instanceof Error ? error : undefined\n );\n } finally {\n // Safety net for early-break paths (UPDATE skip, DeletionPolicy: Retain).\n // removeTask is idempotent, so calling it again after the explicit calls\n // above is a no-op.\n renderer.removeTask(logicalId);\n }\n }\n\n /**\n * Inner body of provisionResource, extracted so the outer wrapper can\n * apply the per-resource deadline (`withResourceDeadline`) without\n * having the timeout / warn timer code dwarf the real provisioning\n * logic. Behaviour is unchanged from the pre-deadline implementation.\n */\n private async provisionResourceBody(\n logicalId: string,\n change: ResourceChange,\n stateResources: Record<string, ResourceState>,\n stackName: string,\n template?: CloudFormationTemplate,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>,\n counts?: { created: number; updated: number; deleted: number; skipped: number },\n progress?: { current: number; total: number }\n ): Promise<void> {\n const resourceType = change.resourceType;\n const provider = this.providerRegistry.getProvider(resourceType);\n const renderer = getLiveRenderer();\n\n switch (change.changeType) {\n case 'CREATE': {\n const desiredProps = change.desiredProperties || {};\n\n // Resolve intrinsic functions in properties\n const context = this.buildResolverContext(\n {\n template: template!,\n resources: stateResources,\n ...(parameterValues && { parameters: parameterValues }),\n ...(conditions && { conditions }),\n },\n stackName\n );\n\n const resolvedProps = (await this.resolver.resolve(desiredProps, context)) as Record<\n string,\n unknown\n >;\n\n // Safety net: if SDK provider doesn't handle all template properties,\n // fall back to CC API for create to ensure no properties are silently dropped\n const { provider: createProvider, properties: createProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n\n const result = await this.withRetry(\n () => createProvider.create(logicalId, resourceType, createProps),\n logicalId,\n undefined,\n undefined,\n provider\n );\n\n // Extract ALL dependencies from template (Ref, Fn::GetAtt, DependsOn)\n // so that deletion order is correct even without implicit type-based deps\n const dependencies = this.extractAllDependencies(template, logicalId);\n const templateAttrs = this.extractTemplateAttributes(template, logicalId);\n\n stateResources[logicalId] = {\n physicalId: result.physicalId,\n resourceType,\n properties: resolvedProps,\n ...(result.attributes && { attributes: result.attributes }),\n ...(dependencies && dependencies.length > 0 && { dependencies }),\n ...templateAttrs,\n };\n\n this.kickOffObservedCapture(\n provider,\n logicalId,\n result.physicalId,\n resourceType,\n resolvedProps\n );\n\n if (counts) counts.created++;\n if (progress) progress.current++;\n const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${createPrefix}✅ ${logicalId} (${resourceType}) created`);\n break;\n }\n\n case 'UPDATE': {\n const currentResource = stateResources[logicalId];\n if (!currentResource) {\n throw new Error(`Cannot update ${logicalId}: resource not found in state`);\n }\n\n const desiredProps = change.desiredProperties || {};\n const currentProps = change.currentProperties || {};\n\n // Resolve intrinsic functions in properties\n const context = this.buildResolverContext(\n {\n template: template!,\n resources: stateResources,\n ...(parameterValues && { parameters: parameterValues }),\n ...(conditions && { conditions }),\n },\n stackName\n );\n\n const resolvedProps = (await this.resolver.resolve(desiredProps, context)) as Record<\n string,\n unknown\n >;\n\n // Re-check diff after resolving intrinsic functions\n // DiffCalculator compares unresolved template vs resolved state, which may produce false positives\n if (JSON.stringify(resolvedProps) === JSON.stringify(currentProps)) {\n // Attribute-only change (schema v5+): `DeletionPolicy` /\n // `UpdateReplacePolicy` may have flipped without any AWS-side\n // property change. There is no per-resource AWS API for those —\n // refresh cdkd state alone and skip the provider call.\n if (change.attributeChanges && change.attributeChanges.length > 0) {\n const attrSummary = change.attributeChanges\n .map((a) => `${a.attribute}: ${a.oldValue ?? '(unset)'} → ${a.newValue ?? '(unset)'}`)\n .join(', ');\n this.logger.info(` ↻ ${logicalId} (${resourceType}) attribute update: ${attrSummary}`);\n stateResources[logicalId] = {\n ...currentResource,\n ...this.extractTemplateAttributes(template, logicalId),\n };\n if (counts) counts.updated++;\n if (progress) progress.current++;\n const attrPrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${attrPrefix}✅ ${logicalId} (${resourceType}) updated (metadata)`);\n break;\n }\n this.logger.debug(\n `Skipping ${logicalId}: no actual changes after intrinsic function resolution`\n );\n if (counts) counts.skipped++;\n break;\n }\n\n // Check if this update requires resource replacement (immutable property changed)\n const needsReplacement = change.propertyChanges?.some((pc) => pc.requiresReplacement);\n\n // Extract ALL dependencies from template (Ref, Fn::GetAtt, DependsOn)\n const dependencies = this.extractAllDependencies(template, logicalId);\n\n if (needsReplacement) {\n // Resource replacement: DELETE old → CREATE new\n const replacedProps = change.propertyChanges\n ?.filter((pc) => pc.requiresReplacement)\n .map((pc) => pc.path)\n .join(', ');\n this.logger.info(\n `Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`\n );\n\n // 1. Create new resource first (CFn order: safe - old resource survives if CREATE fails)\n this.logger.info(` Creating new ${logicalId}...`);\n const { provider: replaceProvider, properties: replaceProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n const createResult = await this.withRetry(\n () => replaceProvider.create(logicalId, resourceType, replaceProps),\n logicalId,\n undefined,\n undefined,\n provider\n );\n\n // 2. Delete old resource (after successful CREATE)\n const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;\n\n if (updateReplacePolicy === 'Retain') {\n this.logger.info(\n ` Retaining old ${logicalId} (${currentResource.physicalId}) - UpdateReplacePolicy: Retain`\n );\n } else {\n this.logger.info(` Deleting old ${logicalId} (${currentResource.physicalId})...`);\n try {\n await provider.delete(\n logicalId,\n currentResource.physicalId,\n resourceType,\n currentResource.properties,\n { expectedRegion: this.stackRegion }\n );\n this.logger.info(` ✓ Old resource deleted`);\n } catch (deleteError) {\n this.logger.warn(\n ` ⚠ Failed to delete old resource ${logicalId} (${currentResource.physicalId}): ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`\n );\n }\n }\n\n stateResources[logicalId] = {\n physicalId: createResult.physicalId,\n resourceType,\n properties: resolvedProps,\n ...(createResult.attributes && { attributes: createResult.attributes }),\n ...(dependencies && dependencies.length > 0 && { dependencies }),\n ...this.extractTemplateAttributes(template, logicalId),\n };\n\n this.kickOffObservedCapture(\n provider,\n logicalId,\n createResult.physicalId,\n resourceType,\n resolvedProps\n );\n\n if (counts) counts.updated++;\n if (progress) progress.current++;\n const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${replacePrefix}✅ ${logicalId} (${resourceType}) replaced`);\n } else {\n // Normal update (in-place)\n this.logger.debug(`Updating ${logicalId} (${resourceType})`);\n\n // Safety net: fall back to CC API if SDK provider doesn't handle all properties\n const { provider: updateProvider, properties: updateProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n\n let result;\n try {\n result = await this.withRetry(\n () =>\n updateProvider.update(\n logicalId,\n currentResource.physicalId,\n resourceType,\n updateProps,\n currentProps\n ),\n logicalId,\n undefined,\n undefined,\n provider\n );\n } catch (updateError) {\n // If UPDATE is not supported (e.g., CC API UnsupportedActionException),\n // fall back to DELETE → CREATE (replacement)\n const msg = updateError instanceof Error ? updateError.message : String(updateError);\n if (\n msg.includes('UnsupportedActionException') ||\n msg.includes('does not support UPDATE')\n ) {\n this.logger.info(\n `UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE → CREATE)`\n );\n try {\n await provider.delete(\n logicalId,\n currentResource.physicalId,\n resourceType,\n currentProps,\n { expectedRegion: this.stackRegion }\n );\n } catch (deleteError) {\n // If old resource doesn't exist (already deleted), proceed with CREATE\n const deleteMsg =\n deleteError instanceof Error ? deleteError.message : String(deleteError);\n if (\n deleteMsg.includes('does not exist') ||\n deleteMsg.includes('not found') ||\n deleteMsg.includes('NotFound')\n ) {\n this.logger.debug(\n `Old resource ${logicalId} already gone, proceeding with CREATE`\n );\n } else {\n throw deleteError;\n }\n }\n const { provider: replProvider, properties: replProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n const createResult = await this.withRetry(\n () => replProvider.create(logicalId, resourceType, replProps),\n logicalId,\n undefined,\n undefined,\n provider\n );\n result = {\n physicalId: createResult.physicalId,\n attributes: createResult.attributes,\n wasReplaced: true,\n };\n } else {\n throw updateError;\n }\n }\n\n if (result.wasReplaced) {\n this.logger.info(\n `Resource ${logicalId} was replaced: ${currentResource.physicalId} -> ${result.physicalId}`\n );\n }\n\n stateResources[logicalId] = {\n physicalId: result.physicalId,\n resourceType,\n properties: resolvedProps,\n ...(result.attributes && { attributes: result.attributes }),\n ...(dependencies && dependencies.length > 0 && { dependencies }),\n ...this.extractTemplateAttributes(template, logicalId),\n };\n\n this.kickOffObservedCapture(\n provider,\n logicalId,\n result.physicalId,\n resourceType,\n resolvedProps\n );\n\n if (counts) counts.updated++;\n if (progress) progress.current++;\n const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${updatePrefix}✅ ${logicalId} (${resourceType}) updated`);\n }\n break;\n }\n\n case 'DELETE': {\n const currentResource = stateResources[logicalId];\n if (!currentResource) {\n throw new Error(`Cannot delete ${logicalId}: resource not found in state`);\n }\n\n // Honor `DeletionPolicy: Retain` / `RetainExceptOnCreate`.\n // State is source of truth as of schema v5+ (cdkd records the\n // attribute on every successful create/update). The synth template\n // is consulted as a fallback for pre-v5 state that has no\n // `state.deletionPolicy` recorded yet — once that resource is\n // re-deployed under v5, the state value takes over and stays\n // authoritative even if the user removes the template attribute\n // mid-flight (a destroy mid-PR would otherwise silently downgrade\n // from Retain to Delete on a transient template edit).\n const deletionPolicy =\n currentResource.deletionPolicy ?? template?.Resources?.[logicalId]?.DeletionPolicy;\n if (shouldRetainResource(deletionPolicy)) {\n this.logger.info(\n `Retaining ${logicalId} (${resourceType}) - DeletionPolicy: ${deletionPolicy}`\n );\n delete stateResources[logicalId];\n break;\n }\n\n this.logger.debug(`Deleting ${logicalId} (${resourceType})`);\n try {\n await this.withRetry(\n () =>\n provider.delete(\n logicalId,\n currentResource.physicalId,\n resourceType,\n currentResource.properties,\n { expectedRegion: this.stackRegion }\n ),\n logicalId,\n 3, // fewer retries for DELETE\n 5_000,\n provider\n );\n } catch (deleteError) {\n const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);\n // Treat \"not found\" errors as success (resource already deleted)\n if (\n msg.includes('does not exist') ||\n msg.includes('was not found') ||\n msg.includes('not found') ||\n msg.includes('No policy found') ||\n msg.includes('NoSuchEntity') ||\n msg.includes('NotFoundException') ||\n msg.includes('ResourceNotFoundException')\n ) {\n this.logger.debug(\n `Resource ${logicalId} already deleted (${msg}), removing from state`\n );\n } else {\n throw deleteError;\n }\n }\n\n delete stateResources[logicalId];\n if (counts) counts.deleted++;\n if (progress) progress.current++;\n const deletePrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${deletePrefix}✅ ${logicalId} (${resourceType}) deleted`);\n break;\n }\n }\n }\n\n /**\n * Create a resource with retry for transient errors\n *\n * Some resources fail immediately after their dependencies are created due to\n * AWS eventual consistency (e.g., Lambda fails if IAM Role hasn't propagated yet).\n * CloudFormation handles this internally; cdkd retries with exponential backoff.\n */\n /**\n * Extract ALL dependencies for a resource from the template.\n *\n * Uses TemplateParser.extractDependencies() to capture Ref, Fn::GetAtt,\n * and DependsOn dependencies. This ensures the state contains complete\n * dependency information for correct deletion ordering (not just DependsOn).\n */\n private extractAllDependencies(\n template: CloudFormationTemplate | undefined,\n logicalId: string\n ): string[] | undefined {\n const resource = template?.Resources?.[logicalId];\n if (!resource) return undefined;\n const parser = new TemplateParser();\n const deps = parser.extractDependencies(resource);\n return deps.size > 0 ? [...deps] : undefined;\n }\n\n /**\n * Read `DeletionPolicy` / `UpdateReplacePolicy` from the synth template\n * so they can be persisted in `ResourceState` (schema v5+). Always returns\n * both keys (`undefined` when the template does not carry the attribute)\n * so that spreading into an existing `ResourceState` reliably overrides a\n * previously-recorded value back to `undefined` — required when the user\n * removes the attribute from their CDK code. `JSON.stringify` then omits\n * the `undefined` keys when state is serialized to S3.\n */\n private extractTemplateAttributes(\n template: CloudFormationTemplate | undefined,\n logicalId: string\n ): {\n deletionPolicy: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n updateReplacePolicy: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n } {\n const resource = template?.Resources?.[logicalId];\n return {\n deletionPolicy: resource?.DeletionPolicy,\n updateReplacePolicy: resource?.UpdateReplacePolicy,\n };\n }\n\n // Type-based implicit deletion ordering rules are defined in\n // src/analyzer/implicit-delete-deps.ts so the deploy DELETE phase and the\n // standalone destroy command apply the same rules.\n\n /**\n * Build a per-resource map of \"must be deleted before me\" dependencies for\n * the DELETE phase, derived from state-recorded dependencies plus implicit\n * type-based ordering rules.\n *\n * For a resource X, the returned set contains every resource Y such that Y\n * must finish deleting before X starts — i.e., Y depends on X (or is otherwise\n * required to vanish first per implicit type rules).\n */\n /**\n * Returns true if the executor still has un-started pending nodes —\n * used to distinguish \"SIGINT cancelled real work\" from \"SIGINT landed\n * after all nodes already completed\" (the latter should not error).\n */\n private hasPending<T>(executor: DagExecutor<T>): boolean {\n for (const node of executor.values()) {\n if (node.state === 'pending') return true;\n }\n return false;\n }\n\n private buildDeletionDependencies(\n deleteIds: Set<string>,\n state: StackState\n ): Map<string, Set<string>> {\n const dependedBy = new Map<string, Set<string>>();\n for (const id of deleteIds) {\n dependedBy.set(id, new Set());\n }\n\n for (const id of deleteIds) {\n const resource = state.resources[id];\n if (!resource?.dependencies) continue;\n for (const dep of resource.dependencies) {\n if (!deleteIds.has(dep)) continue;\n // id depends on dep → dep must be deleted AFTER id (i.e., id is in dep's deletion deps)\n dependedBy.get(dep)!.add(id);\n }\n }\n\n this.addImplicitDeleteDependencies(deleteIds, state, dependedBy);\n\n return dependedBy;\n }\n\n /**\n * Add implicit delete dependency edges based on resource type relationships.\n *\n * Some AWS resources have ordering constraints during deletion that are NOT\n * expressed via Ref/GetAtt in CloudFormation templates. For example, an\n * InternetGateway cannot be deleted until its VPCGatewayAttachment is removed,\n * even though the attachment references the IGW (not the other way around).\n *\n * This method inspects resource types and adds edges so that dependents\n * (e.g., VPCGatewayAttachment) are deleted BEFORE the resources they implicitly\n * depend on (e.g., InternetGateway).\n */\n private addImplicitDeleteDependencies(\n deleteIds: Set<string>,\n state: StackState,\n dependedBy: Map<string, Set<string>>\n ): void {\n // Build a type → logical IDs index for resources being deleted\n const typeToIds = new Map<string, string[]>();\n for (const id of deleteIds) {\n const resource = state.resources[id];\n if (!resource) continue;\n const ids = typeToIds.get(resource.resourceType) ?? [];\n ids.push(id);\n typeToIds.set(resource.resourceType, ids);\n }\n\n for (const id of deleteIds) {\n const resource = state.resources[id];\n if (!resource) continue;\n\n const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];\n if (!mustDeleteAfter) continue;\n\n for (const depType of mustDeleteAfter) {\n const depIds = typeToIds.get(depType);\n if (!depIds) continue;\n\n for (const depId of depIds) {\n // depId (of depType) must be deleted BEFORE id (of resource.resourceType)\n // In the dependedBy map: id is \"depended on\" by depId\n // meaning depId will be picked first (deleted first)\n if (!dependedBy.has(id)) dependedBy.set(id, new Set());\n if (!dependedBy.get(id)!.has(depId)) {\n dependedBy.get(id)!.add(depId);\n this.logger.debug(\n `Implicit delete dependency: ${depId} (${depType}) must be deleted before ${id} (${resource.resourceType})`\n );\n }\n }\n }\n }\n }\n\n /**\n * Select the appropriate provider for create/update, falling back to CC API\n * if the SDK provider doesn't handle all template properties.\n *\n * This safety net prevents properties from being silently dropped when an SDK\n * provider only maps a subset of CloudFormation properties.\n *\n * DELETE always uses the SDK provider (force-delete, cleanup, etc.).\n */\n private selectProviderWithSafetyNet(\n sdkProvider: ResourceProvider,\n resourceType: string,\n resolvedProps: Record<string, unknown>,\n logicalId: string\n ): { provider: ResourceProvider; properties: Record<string, unknown> } {\n const handledSet = sdkProvider.handledProperties?.get(resourceType);\n if (!handledSet) {\n // Provider doesn't declare handledProperties for this type — assume full coverage\n return { provider: sdkProvider, properties: resolvedProps };\n }\n\n const templateProps = Object.keys(resolvedProps);\n const unhandledProps = templateProps.filter((p) => !handledSet.has(p));\n\n if (unhandledProps.length === 0) {\n // All properties are handled by the SDK provider\n return { provider: sdkProvider, properties: resolvedProps };\n }\n\n // There are unhandled properties — try to fall back to CC API\n if (\n CloudControlProvider.isSupportedResourceType(resourceType) &&\n !sdkProvider.disableCcApiFallback\n ) {\n this.logger.info(\n `${logicalId}: SDK provider does not handle [${unhandledProps.join(', ')}] — falling back to CC API for create/update`\n );\n\n // Apply default name generation so CC API uses the same names SDK provider would have.\n // If the provider has custom pre-processing, use that instead.\n const fallbackProps = sdkProvider.preparePropertiesForFallback\n ? sdkProvider.preparePropertiesForFallback(logicalId, resourceType, resolvedProps)\n : applyDefaultNameForFallback(logicalId, resourceType, resolvedProps);\n\n return {\n provider: this.providerRegistry.getCloudControlProvider(),\n properties: fallbackProps,\n };\n }\n\n // CC API fallback not available — fail to prevent silent property loss\n const reason = sdkProvider.disableCcApiFallback\n ? 'CC API fallback is disabled for this provider (known CC API issues)'\n : `CC API does not support ${resourceType}`;\n throw new ProvisioningError(\n `SDK provider for ${resourceType} does not handle properties [${unhandledProps.join(', ')}] ` +\n `and ${reason}. ` +\n `These properties would be silently dropped. ` +\n `Please update the SDK provider to handle all required properties.`,\n resourceType,\n logicalId,\n ''\n );\n }\n\n /**\n * Execute an operation with retry for transient IAM propagation errors.\n *\n * Thin wrapper over `withRetry` from ./retry.js that injects this engine's\n * SIGINT-aware interrupt check and logger. The actual backoff schedule\n * lives there.\n *\n * When the provider opts out via `disableOuterRetry`, the operation is\n * invoked exactly once and the retry loop is skipped entirely. The\n * Custom Resource provider uses this to avoid re-running its `create()`\n * — each invocation derives a fresh pre-signed S3 URL and RequestId,\n * so an outer retry leaves the previous attempt's Lambda response\n * stranded at an S3 key nobody polls.\n */\n private async withRetry<T>(\n operation: () => Promise<T>,\n logicalId: string,\n maxRetries?: number,\n initialDelayMs?: number,\n provider?: ResourceProvider\n ): Promise<T> {\n if (provider?.disableOuterRetry) {\n // Single-shot — provider handles transient errors internally.\n return operation();\n }\n return withRetry(operation, logicalId, {\n ...(maxRetries !== undefined && { maxRetries }),\n ...(initialDelayMs !== undefined && { initialDelayMs }),\n logger: this.logger,\n isInterrupted: () => this.interrupted,\n onInterrupted: () => new InterruptedError(),\n });\n }\n\n /**\n * Resolve stack outputs from template and resource attributes\n *\n * Uses IntrinsicFunctionResolver for full CloudFormation intrinsic function support.\n */\n private async resolveOutputs(\n template: CloudFormationTemplate,\n resources: Record<string, ResourceState>,\n stackName: string,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>\n ): Promise<Record<string, unknown>> {\n if (!template.Outputs) {\n return {};\n }\n\n const outputs: Record<string, unknown> = {};\n const context = this.buildResolverContext(\n {\n template,\n resources,\n ...(parameterValues && { parameters: parameterValues }),\n ...(conditions && { conditions }),\n },\n stackName\n );\n\n for (const [outputKey, output] of Object.entries(template.Outputs)) {\n try {\n const value = await this.resolver.resolve(output.Value, context);\n outputs[outputKey] = value;\n\n // If the output has an Export.Name, also store under that key\n // so Fn::ImportValue can find it by export name\n if (output.Export?.Name) {\n const exportName =\n typeof output.Export.Name === 'string'\n ? output.Export.Name\n : await this.resolver.resolve(output.Export.Name, context);\n if (typeof exportName === 'string') {\n outputs[exportName] = value;\n }\n }\n } catch (error) {\n this.logger.warn(`Failed to resolve output ${outputKey}: ${String(error)}`);\n outputs[outputKey] = undefined;\n }\n }\n\n return outputs;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"deploy-engine-D4t--jpp.js","names":["err","execFileAsync","DescribeImagesCommand","err"],"sources":["../src/provisioning/resource-name.ts","../src/utils/live-renderer.ts","../src/utils/stack-context.ts","../src/utils/logger.ts","../src/utils/error-handler.ts","../src/utils/aws-region-resolver.ts","../src/synthesis/app-executor.ts","../src/types/assembly.ts","../src/synthesis/assembly-reader.ts","../src/synthesis/context-store.ts","../src/synthesis/context-providers/az-provider.ts","../src/synthesis/context-providers/ssm-provider.ts","../src/synthesis/context-providers/hosted-zone-provider.ts","../src/synthesis/context-providers/vpc-provider.ts","../src/synthesis/context-providers/cc-api-provider.ts","../src/synthesis/context-providers/ami-provider.ts","../src/synthesis/context-providers/security-group-provider.ts","../src/synthesis/context-providers/load-balancer-provider.ts","../src/synthesis/context-providers/key-provider.ts","../src/synthesis/context-providers/index.ts","../src/cli/config-loader.ts","../src/synthesis/synthesizer.ts","../src/assets/file-asset-publisher.ts","../src/assets/docker-build.ts","../src/assets/docker-asset-publisher.ts","../src/deployment/work-graph.ts","../src/utils/stringify.ts","../src/assets/asset-publisher.ts","../src/types/state.ts","../src/state/s3-state-backend.ts","../src/state/lock-manager.ts","../src/analyzer/template-parser.ts","../src/analyzer/lambda-vpc-deps.ts","../src/analyzer/cdk-defensive-deps.ts","../src/analyzer/dag-builder.ts","../src/analyzer/replacement-rules.ts","../src/analyzer/diff-calculator.ts","../src/deployment/intrinsic-function-resolver.ts","../src/provisioning/json-patch-generator.ts","../src/provisioning/region-check.ts","../src/provisioning/cloud-control-provider.ts","../src/provisioning/providers/custom-resource-provider.ts","../src/provisioning/provider-registry.ts","../src/provisioning/import-helpers.ts","../src/provisioning/providers/iam-role-provider.ts","../src/deployment/dag-executor.ts","../src/analyzer/implicit-delete-deps.ts","../src/deployment/retryable-errors.ts","../src/deployment/retry.ts","../src/deployment/resource-deadline.ts","../src/deployment/deploy-engine.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { createHash } from 'node:crypto';\n\n/**\n * Per-async-context stack name. Resource-name generation reads this so that\n * concurrent deploys (`cdkd deploy --all` runs stacks in parallel up to\n * `--stack-concurrency`) don't fight over a single shared variable.\n *\n * History: this was `let currentStackName: string | undefined` until\n * 2026-05-01. Two parallel `deploy()` calls would each call\n * `setCurrentStackName(...)` and the second would overwrite the first;\n * any IAM Role / SQS Queue / etc. created by the first stack while the\n * second was active would get the second stack's prefix in its physical\n * name, then the second stack's own create attempt for the same logical\n * id would collide (\"Role with name X already exists\"). Switching to\n * `AsyncLocalStorage` scopes the value to each deploy's async chain.\n */\nconst stackNameStore = new AsyncLocalStorage<string>();\n\n/**\n * Run `fn` with `stackName` set as the stack name visible to\n * `generateResourceName` for the duration of the callback (and any\n * `await`s inside). Concurrent invocations each get an independent scope\n * — this is the safe API for parallel deploys.\n */\nexport function withStackName<T>(stackName: string, fn: () => Promise<T>): Promise<T>;\nexport function withStackName<T>(stackName: string, fn: () => T): T;\nexport function withStackName<T>(stackName: string, fn: () => T | Promise<T>): T | Promise<T> {\n return stackNameStore.run(stackName, fn);\n}\n\n/**\n * Read the current async context's stack name, if any.\n *\n * Returns `undefined` outside any `withStackName` / `setCurrentStackName`\n * scope. Used by the live renderer to scope per-stack in-flight task\n * entries so concurrent deploys don't clobber each other's tasks (same\n * `logicalId` in two stacks would collide on the singleton renderer's\n * task Map without this).\n */\nexport function getCurrentStackName(): string | undefined {\n return stackNameStore.getStore();\n}\n\n/**\n * Set the current async context's stack name.\n *\n * @deprecated Use {@link withStackName} for new code — it makes the scope\n * obvious at the call site. This setter now uses\n * `AsyncLocalStorage.enterWith` so it remains safe under\n * `--stack-concurrency > 1` (each `deploy()` call has its own async\n * resource, so the value does NOT leak across siblings), but\n * `withStackName` is structurally clearer.\n */\nexport function setCurrentStackName(stackName: string): void {\n stackNameStore.enterWith(stackName);\n}\n\n/**\n * Per-async-context \"skip the stack-name prefix on user-supplied physical\n * names\" flag. Read by `generateResourceName` when its caller passes\n * `userSupplied: true`; auto-generated-name paths\n * (`generateResourceName(logicalId, ...)`) ignore this flag.\n *\n * Scoped via AsyncLocalStorage so that `--stack-concurrency > 1` runs\n * cannot cross-contaminate — each deploy's body is wrapped in its own\n * `withSkipPrefix(...)` scope (the deploy CLI plumbs the resolved\n * `--no-prefix-user-supplied-names` value through here). Default\n * `false` preserves pre-PR behavior when the flag is not set.\n */\nconst skipPrefixStore = new AsyncLocalStorage<boolean>();\n\n/**\n * Run `fn` with the \"skip prefix on user-supplied names\" flag set to\n * `skip`. Mirrors {@link withStackName} — concurrent invocations each\n * get an independent scope so parallel deploys do not fight over a\n * single shared variable.\n *\n * Wrap this around `withStackName(...)` (innermost is `fn`) in the\n * deploy CLI: `withSkipPrefix(flag, () => withStackName(name, body))`.\n * Order does not matter — the two stores are independent — but\n * consistent ordering keeps the call sites readable.\n */\nexport function withSkipPrefix<T>(skip: boolean, fn: () => Promise<T>): Promise<T>;\nexport function withSkipPrefix<T>(skip: boolean, fn: () => T): T;\nexport function withSkipPrefix<T>(skip: boolean, fn: () => T | Promise<T>): T | Promise<T> {\n return skipPrefixStore.run(skip, fn);\n}\n\n/**\n * Read the current async context's skip-prefix flag. Defaults to\n * `false` when no `withSkipPrefix` scope is active.\n *\n * Public for unit tests; `generateResourceName` consumes this\n * internally.\n */\nexport function getCurrentSkipPrefix(): boolean {\n return skipPrefixStore.getStore() ?? false;\n}\n\n/**\n * Resource types whose pre-PR #297 code path ran user-supplied\n * physical names through `generateResourceName` (= got the stack-name\n * prefix). These are the only types affected by\n * `--no-prefix-user-supplied-names`; flipping the flag against an\n * existing stack proposes REPLACEMENT on every state resource of\n * one of these types whose `physicalId` is still prefixed.\n *\n * Pattern A providers (Lambda, S3, SNS, SQS, DynamoDB, Logs LogGroup,\n * Events Rule, etc.) historically short-circuited user-supplied names\n * **out** of `generateResourceName` entirely — those types never got\n * the prefix regardless of the flag, so they are NOT included here.\n *\n * Used by the deploy-time pre-flight migration check in\n * `src/cli/commands/prefix-migration-check.ts`. Keep in sync with the\n * Pattern B provider call sites that consume\n * `generateResourceNameWithFallback(...)`.\n */\nexport const PATTERN_B_RESOURCE_TYPES: readonly string[] = [\n 'AWS::IAM::Role',\n 'AWS::IAM::User',\n 'AWS::IAM::Group',\n 'AWS::IAM::InstanceProfile',\n 'AWS::ElasticLoadBalancingV2::LoadBalancer',\n 'AWS::ElasticLoadBalancingV2::TargetGroup',\n] as const;\n\n/**\n * For each Pattern B resource type, the CFn template `Properties` field\n * the user sets to supply a physical name (`new iam.Role(this, 'X',\n * { roleName: 'my-role' })` → `Properties.RoleName: 'my-role'`).\n *\n * Used by the prefix-migration check to distinguish user-supplied\n * physical names (which the v0.94.0 default flip would actually\n * REPLACE on next deploy) from auto-generated logical-id-fallback\n * names (which keep the prefix in BOTH old and new default — no\n * REPLACE pending). Without this discriminator, the migration\n * check naively flags every prefix-style physicalId regardless of\n * its origin, surfacing a false-positive WARNING on auto-generated\n * names that won't actually be touched.\n *\n * Keep in sync with `PATTERN_B_RESOURCE_TYPES` and with each\n * provider's `Properties[<NameField>]` lookup in\n * `src/provisioning/providers/`.\n */\nexport const PATTERN_B_NAME_PROPERTIES: Readonly<Record<string, string>> = {\n 'AWS::IAM::Role': 'RoleName',\n 'AWS::IAM::User': 'UserName',\n 'AWS::IAM::Group': 'GroupName',\n 'AWS::IAM::InstanceProfile': 'InstanceProfileName',\n 'AWS::ElasticLoadBalancingV2::LoadBalancer': 'Name',\n 'AWS::ElasticLoadBalancingV2::TargetGroup': 'Name',\n};\n\n/**\n * Options for generating a resource name.\n */\nexport interface ResourceNameOptions {\n /** Maximum length for the name (e.g., 32 for ALB/TG, 64 for IAM, 63 for S3) */\n maxLength: number;\n /** Whether to force lowercase (e.g., S3 buckets) */\n lowercase?: boolean;\n /** Allowed character regex pattern. Characters not matching will be removed.\n * Default: /[^a-zA-Z0-9-]/ (alphanumeric + hyphen) */\n allowedPattern?: RegExp;\n /**\n * `true` when the caller is passing a name the user explicitly\n * declared in their CDK code (e.g. `new iam.Role(this, 'X', {\n * roleName: 'my-role' })`). `false` (default) when the caller is\n * passing the logical-id fallback or any other cdkd-generated value.\n *\n * Combined with the per-deploy `withSkipPrefix(true)` flag, a\n * `userSupplied: true` call skips the stack-name prefix and returns\n * the user's declared name verbatim (after the same sanitize /\n * truncate pipeline). When `userSupplied` is `false` OR\n * `withSkipPrefix` is unset / `false`, the stack-name prefix is\n * applied (pre-PR behavior).\n *\n * This split is load-bearing: cdkd's stack-scoping concern (prefix\n * for cross-stack uniqueness on auto-generated names) must stay\n * coupled to the auto-generated path, NOT to user-declared names —\n * those belong to the user.\n */\n userSupplied?: boolean;\n}\n\n/**\n * Generate a unique resource name from the logical ID.\n *\n * Generates names in CloudFormation-compatible format:\n * `{StackName}-{LogicalId}-{Hash}` (truncated to maxLength).\n *\n * @param name The raw name (from properties or logicalId fallback)\n * @param options Length and character constraints\n * @returns A sanitized, truncated name that fits the constraints\n */\nexport function generateResourceName(name: string, options: ResourceNameOptions): string {\n const {\n maxLength,\n lowercase = false,\n allowedPattern = /[^a-zA-Z0-9-]/g,\n userSupplied = false,\n } = options;\n\n // Include stack name for uniqueness (like CloudFormation does).\n //\n // The prefix is suppressed when the caller marked the name as\n // user-supplied AND the per-deploy `withSkipPrefix(true)` flag is\n // active — the user owns that name and cdkd should not rewrite it.\n // Every other path (logical-id fallback, no withSkipPrefix scope,\n // flag set to false) keeps the prefix for cross-stack uniqueness.\n const currentStackName = stackNameStore.getStore();\n const shouldPrefix = currentStackName && !(userSupplied && getCurrentSkipPrefix());\n const fullName = shouldPrefix ? `${currentStackName}-${name}` : name;\n\n // Apply lowercase BEFORE pattern matching (so A-Z aren't removed by /[^a-z0-9.-]/)\n let sanitized = lowercase ? fullName.toLowerCase() : fullName;\n sanitized = sanitized.replace(allowedPattern, '-');\n\n // Collapse consecutive hyphens and remove leading/trailing\n sanitized = sanitized.replace(/-{2,}/g, '-').replace(/^-+|-+$/g, '');\n\n if (sanitized.length <= maxLength) {\n return sanitized;\n }\n\n // Truncate with hash suffix for uniqueness\n const hash = createHash('sha256').update(fullName).digest('hex').substring(0, 8);\n const maxPrefixLength = maxLength - hash.length - 1; // -1 for separator\n const prefix = sanitized.substring(0, maxPrefixLength).replace(/-+$/, '');\n\n return `${prefix}-${hash}`;\n}\n\n/**\n * Generate a resource name from a user-declared physical name OR\n * fall back to the logical id.\n *\n * Wraps {@link generateResourceName} to express the Pattern B call-site\n * shape (`generateResourceName((properties['Name'] as string | undefined)\n * || logicalId, opts)`) as a single typed helper. The user-supplied\n * branch passes `userSupplied: true`, which makes the per-deploy\n * `withSkipPrefix(true)` flag drop the stack-name prefix on that name.\n * The fallback (logical-id) branch is `userSupplied: false` and keeps\n * the prefix regardless of the flag — auto-generated names rely on\n * the prefix for cross-stack uniqueness.\n *\n * Use at every Pattern B provider call site (currently IAM Role, IAM\n * User, IAM Group, IAM InstanceProfile, ELBv2 LoadBalancer, ELBv2\n * TargetGroup) so the `--no-prefix-user-supplied-names` flag controls\n * those types consistently. Pattern A providers (Lambda, S3, SNS,\n * SQS, DynamoDB, etc.) do NOT need this helper — they already\n * short-circuit the user-supplied name out of the\n * `generateResourceName` call entirely, so the prefix is never\n * applied to user-supplied names regardless of the flag.\n */\nexport function generateResourceNameWithFallback(\n userSuppliedName: string | undefined,\n logicalId: string,\n options: Omit<ResourceNameOptions, 'userSupplied'>\n): string {\n if (userSuppliedName !== undefined && userSuppliedName !== '') {\n return generateResourceName(userSuppliedName, { ...options, userSupplied: true });\n }\n return generateResourceName(logicalId, { ...options, userSupplied: false });\n}\n\n/**\n * Default name generation rules for CC API fallback.\n *\n * When an SDK provider falls back to CC API, the resource may need a\n * default name that the SDK provider would have generated. This map\n * defines the name property and generation options for each resource type.\n *\n * Format: resourceType → { nameProperty, options, postProcess? }\n */\nconst FALLBACK_NAME_RULES: Record<\n string,\n {\n nameProperty: string;\n options: ResourceNameOptions;\n }\n> = {\n 'AWS::S3::Bucket': { nameProperty: 'BucketName', options: { maxLength: 63, lowercase: true } },\n 'AWS::SQS::Queue': { nameProperty: 'QueueName', options: { maxLength: 80 } },\n 'AWS::SNS::Topic': { nameProperty: 'TopicName', options: { maxLength: 256 } },\n 'AWS::Lambda::Function': { nameProperty: 'FunctionName', options: { maxLength: 64 } },\n 'AWS::Lambda::LayerVersion': { nameProperty: 'LayerName', options: { maxLength: 64 } },\n 'AWS::IAM::Role': { nameProperty: 'RoleName', options: { maxLength: 64 } },\n 'AWS::IAM::Policy': { nameProperty: 'PolicyName', options: { maxLength: 64 } },\n 'AWS::IAM::User': { nameProperty: 'UserName', options: { maxLength: 64 } },\n 'AWS::IAM::Group': { nameProperty: 'GroupName', options: { maxLength: 128 } },\n 'AWS::IAM::InstanceProfile': {\n nameProperty: 'InstanceProfileName',\n options: { maxLength: 128 },\n },\n 'AWS::DynamoDB::Table': { nameProperty: 'TableName', options: { maxLength: 255 } },\n 'AWS::ECR::Repository': {\n nameProperty: 'RepositoryName',\n options: { maxLength: 256, lowercase: true },\n },\n 'AWS::ECS::Cluster': { nameProperty: 'ClusterName', options: { maxLength: 255 } },\n 'AWS::ECS::Service': { nameProperty: 'ServiceName', options: { maxLength: 255 } },\n 'AWS::Logs::LogGroup': { nameProperty: 'LogGroupName', options: { maxLength: 512 } },\n 'AWS::CloudWatch::Alarm': { nameProperty: 'AlarmName', options: { maxLength: 256 } },\n 'AWS::Events::Rule': { nameProperty: 'Name', options: { maxLength: 64 } },\n 'AWS::Events::EventBus': { nameProperty: 'Name', options: { maxLength: 256 } },\n 'AWS::Kinesis::Stream': { nameProperty: 'Name', options: { maxLength: 128 } },\n 'AWS::StepFunctions::StateMachine': {\n nameProperty: 'StateMachineName',\n options: { maxLength: 80 },\n },\n 'AWS::SecretsManager::Secret': {\n nameProperty: 'Name',\n options: { maxLength: 512, allowedPattern: /[^a-zA-Z0-9-/_]/g },\n },\n 'AWS::SSM::Parameter': { nameProperty: 'Name', options: { maxLength: 2048 } },\n 'AWS::Cognito::UserPool': { nameProperty: 'UserPoolName', options: { maxLength: 128 } },\n 'AWS::ElastiCache::SubnetGroup': {\n nameProperty: 'CacheSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::ElastiCache::CacheCluster': {\n nameProperty: 'ClusterName',\n options: { maxLength: 40, lowercase: true },\n },\n 'AWS::RDS::DBSubnetGroup': {\n nameProperty: 'DBSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::RDS::DBCluster': {\n nameProperty: 'DBClusterIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::RDS::DBInstance': {\n nameProperty: 'DBInstanceIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n // DocumentDB — RDS-shaped API; same name constraints.\n 'AWS::DocDB::DBSubnetGroup': {\n nameProperty: 'DBSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::DocDB::DBCluster': {\n nameProperty: 'DBClusterIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::DocDB::DBInstance': {\n nameProperty: 'DBInstanceIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n // Neptune — RDS-shaped API; same name constraints.\n 'AWS::Neptune::DBSubnetGroup': {\n nameProperty: 'DBSubnetGroupName',\n options: { maxLength: 255, lowercase: true },\n },\n 'AWS::Neptune::DBCluster': {\n nameProperty: 'DBClusterIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::Neptune::DBInstance': {\n nameProperty: 'DBInstanceIdentifier',\n options: { maxLength: 63, lowercase: true },\n },\n 'AWS::ElasticLoadBalancingV2::LoadBalancer': {\n nameProperty: 'Name',\n options: { maxLength: 32 },\n },\n 'AWS::ElasticLoadBalancingV2::TargetGroup': {\n nameProperty: 'Name',\n options: { maxLength: 32 },\n },\n 'AWS::WAFv2::WebACL': { nameProperty: 'Name', options: { maxLength: 128 } },\n 'AWS::CodeBuild::Project': { nameProperty: 'Name', options: { maxLength: 255 } },\n 'AWS::S3Express::DirectoryBucket': {\n nameProperty: 'BucketName',\n options: { maxLength: 63, lowercase: true },\n },\n};\n\n/**\n * Apply default name generation for CC API fallback.\n *\n * When a resource doesn't have an explicit name property set,\n * generates the same default name that the SDK provider would have created.\n * This ensures consistent naming regardless of whether SDK or CC API handles the resource.\n *\n * @param logicalId Logical ID from the template\n * @param resourceType CloudFormation resource type\n * @param properties Resource properties (will not be mutated)\n * @returns Properties with default name applied if needed, or original properties if no rule exists\n */\nexport function applyDefaultNameForFallback(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n): Record<string, unknown> {\n const rule = FALLBACK_NAME_RULES[resourceType];\n if (!rule) return properties;\n\n // If the name property is already set, no need to generate\n if (properties[rule.nameProperty]) return properties;\n\n const generatedName = generateResourceName(logicalId, rule.options);\n\n return {\n ...properties,\n [rule.nameProperty]: generatedName,\n };\n}\n","/**\n * Live multi-line progress renderer for the bottom of the terminal.\n *\n * Maintains a \"live area\" listing in-flight tasks (Creating MyBucket...),\n * redrawn on a spinner timer. Other log output is routed through\n * {@link LiveRenderer.printAbove} so it appears above the live area without\n * disturbing the currently-displayed in-flight tasks.\n *\n * Design notes:\n * - Multiple resources can be in flight concurrently (cdkd uses parallel DAG\n * dispatch), so a single in-place line overwrite is not enough — each\n * in-flight resource is its own line in the live area.\n * - On non-TTY (CI/log-collection), the renderer stays inactive and\n * {@link LiveRenderer.printAbove} falls through to a direct write, so output\n * matches the previous append-only behavior.\n * - In verbose mode (debug level) the caller should not start the renderer:\n * debug logs would interleave too aggressively with the live area.\n */\n\nimport { getCurrentStackName } from '../provisioning/resource-name.js';\n\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\nconst FRAME_INTERVAL_MS = 80;\nconst ESC = '\\x1b[';\n\ninterface Task {\n label: string;\n startedAt: number;\n /** Stack the task belongs to (from `withStackName` AsyncLocalStorage),\n * or `undefined` when addTask was called outside any stack scope.\n * Captured at addTask time because the spinner re-draw runs on a\n * setInterval timer with no inherited async context. */\n stackName: string | undefined;\n}\n\n/**\n * Scope a task `id` to its calling stack so two stacks running in\n * parallel — `cdkd deploy --all` with `--stack-concurrency > 1` — don't\n * collide on the same `logicalId` in the renderer's task Map. Without\n * this, stack B's `addTask('MyQueue', ...)` would overwrite stack A's\n * entry, and stack A's later `removeTask('MyQueue')` would erase\n * stack B's.\n */\nfunction scopedKey(id: string, stackName: string | undefined): string {\n return stackName ? `${stackName}:${id}` : id;\n}\n\nexport class LiveRenderer {\n private tasks = new Map<string, Task>();\n private active = false;\n private spinnerIndex = 0;\n private interval: NodeJS.Timeout | null = null;\n private linesDrawn = 0;\n private cursorHidden = false;\n private exitListener: (() => void) | null = null;\n private readonly stream: NodeJS.WriteStream;\n\n constructor(stream: NodeJS.WriteStream = process.stdout) {\n this.stream = stream;\n }\n\n isActive(): boolean {\n return this.active;\n }\n\n /**\n * Enable the live renderer. No-op if stdout is not a TTY or if\n * `CDKD_NO_LIVE=1`. Returns true if successfully enabled.\n */\n start(): boolean {\n if (this.active) return true;\n if (!this.stream.isTTY) return false;\n if (process.env['CDKD_NO_LIVE'] === '1') return false;\n\n this.active = true;\n this.hideCursor();\n // Restore the cursor on abrupt process exit (e.g., uncaught exception\n // before stop() runs). Removed in stop() to avoid leaking listeners\n // across renderer instances.\n if (!this.exitListener) {\n this.exitListener = () => this.showCursor();\n process.on('exit', this.exitListener);\n }\n this.interval = setInterval(() => this.draw(), FRAME_INTERVAL_MS);\n if (typeof this.interval.unref === 'function') this.interval.unref();\n return true;\n }\n\n stop(): void {\n if (!this.active) return;\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n this.clear();\n this.showCursor();\n if (this.exitListener) {\n process.removeListener('exit', this.exitListener);\n this.exitListener = null;\n }\n this.tasks.clear();\n this.active = false;\n }\n\n addTask(id: string, label: string): void {\n const stackName = getCurrentStackName();\n this.tasks.set(scopedKey(id, stackName), { label, startedAt: Date.now(), stackName });\n if (this.active) this.draw();\n }\n\n removeTask(id: string): void {\n const stackName = getCurrentStackName();\n if (!this.tasks.delete(scopedKey(id, stackName))) return;\n if (this.active) this.draw();\n }\n\n /**\n * Replace the label of a previously-added task in place. No-op if the\n * task is not currently tracked (e.g. it already finished). Used by the\n * per-resource deadline wrapper to surface a \"[taking longer than\n * expected, Nm+]\" suffix without disturbing the elapsed-time counter\n * the renderer tracks via `startedAt`.\n */\n updateTaskLabel(id: string, label: string): void {\n const stackName = getCurrentStackName();\n const task = this.tasks.get(scopedKey(id, stackName));\n if (!task) return;\n task.label = label;\n if (this.active) this.draw();\n }\n\n /**\n * Print content above the live area. Clears the live area, runs the writer,\n * then redraws the live area. When the renderer is inactive, the writer\n * runs directly so callers can use this unconditionally.\n */\n printAbove(write: () => void): void {\n if (!this.active) {\n write();\n return;\n }\n this.clear();\n write();\n this.draw();\n }\n\n private clear(): void {\n if (this.linesDrawn === 0) return;\n this.stream.write('\\r');\n for (let i = 0; i < this.linesDrawn; i++) {\n this.stream.write(`${ESC}1A${ESC}2K`);\n }\n this.linesDrawn = 0;\n }\n\n private draw(): void {\n if (!this.active) return;\n this.clear();\n if (this.tasks.size === 0) return;\n\n const frame = SPINNER_FRAMES[this.spinnerIndex % SPINNER_FRAMES.length]!;\n this.spinnerIndex++;\n\n // When tasks from more than one stack are in flight at the same time,\n // prefix each line with the stack name so the user can tell them\n // apart. In single-stack runs, omit the prefix to keep the area\n // visually clean. (Detection is per-frame: as soon as a second\n // stack adds its first task, the prefix appears on every row;\n // when the second stack's tasks all complete and only one stack\n // remains, the prefix disappears.)\n const distinctStacks = new Set<string | undefined>();\n for (const task of this.tasks.values()) distinctStacks.add(task.stackName);\n const showStackPrefix = distinctStacks.size > 1;\n\n // Truncate to terminal width so a long label does not wrap and confuse\n // the line-up clear logic. Default to 80 if columns is unavailable.\n const cols = this.stream.columns ?? 80;\n const lines: string[] = [];\n for (const task of this.tasks.values()) {\n const elapsed = ((Date.now() - task.startedAt) / 1000).toFixed(1);\n const prefix = showStackPrefix && task.stackName ? `[${task.stackName}] ` : '';\n const raw = ` ${frame} ${prefix}${task.label} (${elapsed}s)`;\n lines.push(this.truncate(raw, cols));\n }\n this.stream.write(lines.join('\\n') + '\\n');\n this.linesDrawn = lines.length;\n }\n\n private truncate(s: string, maxLen: number): string {\n if (s.length <= maxLen) return s;\n if (maxLen <= 1) return '…';\n return s.substring(0, maxLen - 1) + '…';\n }\n\n private hideCursor(): void {\n if (this.cursorHidden) return;\n this.stream.write(`${ESC}?25l`);\n this.cursorHidden = true;\n }\n\n private showCursor(): void {\n if (!this.cursorHidden) return;\n this.stream.write(`${ESC}?25h`);\n this.cursorHidden = false;\n }\n}\n\nlet globalRenderer: LiveRenderer | null = null;\n\nexport function getLiveRenderer(): LiveRenderer {\n if (!globalRenderer) globalRenderer = new LiveRenderer();\n return globalRenderer;\n}\n\n/**\n * Reset the singleton (for tests).\n */\nexport function resetLiveRenderer(): void {\n if (globalRenderer) globalRenderer.stop();\n globalRenderer = null;\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * Per-stack log buffer for parallel multi-stack deploys.\n *\n * Without buffering, two concurrent `deploy()` calls interleave their\n * `logger.info(...)` lines: stack A's \"Changes: 4 to create\" appears in\n * the middle of stack B's `[N/N] ✅ ...` progress, and stack B's\n * \"Deployment completed\" lands after stack A's late progress lines. With\n * buffering, each stack's log output is captured into its own buffer\n * for the duration of the deploy and flushed atomically when the deploy\n * finishes — so the user sees one clean per-stack block.\n *\n * Single-stack deploys do NOT enable buffering (the caller checks\n * `stacks.length > 1`); real-time output is preferred when there is no\n * interleaving risk.\n */\nexport interface StackOutputBuffer {\n lines: string[];\n}\n\nconst outputBufferStore = new AsyncLocalStorage<StackOutputBuffer>();\n\n/**\n * Run `fn` with a fresh log buffer scoped to its async chain. Any\n * `logger.info / debug / warn / error` calls inside `fn` (and any\n * `await`s) push into the buffer instead of writing to stdout/stderr.\n * Returns the buffered lines (and either `result` or `error`) so the\n * caller can flush them in one block.\n */\nexport async function runStackBuffered<T>(\n fn: () => Promise<T>\n): Promise<{ lines: string[] } & ({ ok: true; result: T } | { ok: false; error: unknown })> {\n const buffer: StackOutputBuffer = { lines: [] };\n return outputBufferStore.run(buffer, async () => {\n try {\n const result = await fn();\n return { ok: true, result, lines: buffer.lines };\n } catch (error) {\n return { ok: false, error, lines: buffer.lines };\n }\n });\n}\n\n/**\n * Get the current async context's stack output buffer, or `undefined`\n * if no `runStackBuffered` is active. The logger consults this on every\n * call: present → push to buffer; absent → fall through to live\n * renderer / console.\n */\nexport function getCurrentStackOutputBuffer(): StackOutputBuffer | undefined {\n return outputBufferStore.getStore();\n}\n","import type { Logger, LogLevel } from '../types/config.js';\nimport { getLiveRenderer } from './live-renderer.js';\nimport { getCurrentStackOutputBuffer } from './stack-context.js';\n\n/**\n * ANSI color codes\n */\nconst colors = {\n reset: '\\x1b[0m',\n bright: '\\x1b[1m',\n dim: '\\x1b[2m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n blue: '\\x1b[34m',\n cyan: '\\x1b[36m',\n gray: '\\x1b[90m',\n} as const;\n\n/**\n * Format timestamp\n */\nfunction formatTimestamp(): string {\n const now = new Date();\n return now.toISOString();\n}\n\n/**\n * Console logger implementation\n *\n * Supports two output modes:\n * - verbose (debug level): timestamps, module prefixes, all details\n * - compact (info level): clean output without timestamps or prefixes\n */\nexport class ConsoleLogger implements Logger {\n private level: LogLevel;\n private useColors: boolean;\n\n constructor(level: LogLevel = 'info', useColors: boolean = true) {\n this.level = level;\n this.useColors = useColors;\n }\n\n private shouldLog(level: LogLevel): boolean {\n const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];\n const currentLevelIndex = levels.indexOf(this.level);\n const messageLevelIndex = levels.indexOf(level);\n return messageLevelIndex >= currentLevelIndex;\n }\n\n private formatMessage(level: LogLevel, message: string, ...args: unknown[]): string {\n const formattedArgs = args.length > 0 ? ' ' + args.map((a) => JSON.stringify(a)).join(' ') : '';\n\n // Verbose mode: full timestamps and level\n if (this.level === 'debug') {\n const timestamp = formatTimestamp();\n const levelStr = level.toUpperCase().padEnd(5);\n\n if (this.useColors) {\n const levelColor = {\n debug: colors.gray,\n info: colors.blue,\n warn: colors.yellow,\n error: colors.red,\n }[level];\n\n return `${colors.dim}${timestamp}${colors.reset} ${levelColor}${levelStr}${colors.reset} ${message}${formattedArgs}`;\n }\n\n return `${timestamp} ${levelStr} ${message}${formattedArgs}`;\n }\n\n // Compact mode: clean output\n if (this.useColors) {\n if (level === 'error') {\n return `${colors.red}${message}${formattedArgs}${colors.reset}`;\n }\n if (level === 'warn') {\n return `${colors.yellow}${message}${formattedArgs}${colors.reset}`;\n }\n return `${message}${formattedArgs}`;\n }\n\n return `${message}${formattedArgs}`;\n }\n\n /**\n * Route a formatted log line. When a per-stack output buffer is active in\n * the current async context (parallel multi-stack deploy), capture the\n * line into the buffer so it can be flushed as one atomic block when the\n * stack finishes. Otherwise fall through to the live renderer / console\n * as before.\n */\n private emit(level: LogLevel, formatted: string): void {\n const buffer = getCurrentStackOutputBuffer();\n if (buffer) {\n buffer.lines.push(formatted);\n return;\n }\n getLiveRenderer().printAbove(() => {\n if (level === 'error') console.error(formatted);\n else if (level === 'warn') console.warn(formatted);\n else if (level === 'info') console.info(formatted);\n else console.debug(formatted);\n });\n }\n\n debug(message: string, ...args: unknown[]): void {\n if (this.shouldLog('debug')) {\n this.emit('debug', this.formatMessage('debug', message, ...args));\n }\n }\n\n info(message: string, ...args: unknown[]): void {\n if (this.shouldLog('info')) {\n this.emit('info', this.formatMessage('info', message, ...args));\n }\n }\n\n warn(message: string, ...args: unknown[]): void {\n if (this.shouldLog('warn')) {\n this.emit('warn', this.formatMessage('warn', message, ...args));\n }\n }\n\n error(message: string, ...args: unknown[]): void {\n if (this.shouldLog('error')) {\n this.emit('error', this.formatMessage('error', message, ...args));\n }\n }\n\n /**\n * Set log level\n */\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n getLevel(): LogLevel {\n return this.level;\n }\n\n /**\n * Create a child logger with a prefix\n *\n * In verbose mode, prefix is shown as [Prefix]. In compact mode, prefix is hidden.\n */\n child(prefix: string): ChildLogger {\n return new ChildLogger(prefix, this.useColors);\n }\n}\n\n/**\n * Child logger that always syncs level from global logger\n */\nclass ChildLogger extends ConsoleLogger {\n private readonly prefix: string;\n\n constructor(prefix: string, useColors: boolean) {\n super('info', useColors);\n this.prefix = prefix;\n }\n\n private syncLevel(): void {\n if (globalLogger) {\n this.setLevel(globalLogger.getLevel());\n }\n }\n\n override debug(message: string, ...args: unknown[]): void {\n this.syncLevel();\n super.debug(`[${this.prefix}] ${message}`, ...args);\n }\n\n override info(message: string, ...args: unknown[]): void {\n this.syncLevel();\n const msg = this.getLevel() === 'debug' ? `[${this.prefix}] ${message}` : message;\n super.info(msg, ...args);\n }\n\n override warn(message: string, ...args: unknown[]): void {\n this.syncLevel();\n const msg = this.getLevel() === 'debug' ? `[${this.prefix}] ${message}` : message;\n super.warn(msg, ...args);\n }\n\n override error(message: string, ...args: unknown[]): void {\n this.syncLevel();\n const msg = this.getLevel() === 'debug' ? `[${this.prefix}] ${message}` : message;\n super.error(msg, ...args);\n }\n}\n\n/**\n * Global logger instance\n */\nlet globalLogger: ConsoleLogger | null = null;\n\n/**\n * Get or create global logger\n */\nexport function getLogger(): ConsoleLogger {\n if (!globalLogger) {\n globalLogger = new ConsoleLogger();\n }\n return globalLogger;\n}\n\n/**\n * Set global logger instance\n */\nexport function setLogger(logger: ConsoleLogger): void {\n globalLogger = logger;\n}\n","import { getLogger } from './logger.js';\n\n/**\n * Base error class for cdkd\n */\nexport class CdkdError 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 = 'CdkdError';\n Object.setPrototypeOf(this, CdkdError.prototype);\n }\n}\n\n/**\n * State management errors\n */\nexport class StateError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'STATE_ERROR', cause);\n this.name = 'StateError';\n Object.setPrototypeOf(this, StateError.prototype);\n }\n}\n\n/**\n * Lock acquisition errors\n */\nexport class LockError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'LOCK_ERROR', cause);\n this.name = 'LockError';\n Object.setPrototypeOf(this, LockError.prototype);\n }\n}\n\n/**\n * Synthesis errors\n */\nexport class SynthesisError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'SYNTHESIS_ERROR', cause);\n this.name = 'SynthesisError';\n Object.setPrototypeOf(this, SynthesisError.prototype);\n }\n}\n\n/**\n * Asset errors\n */\nexport class AssetError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'ASSET_ERROR', cause);\n this.name = 'AssetError';\n Object.setPrototypeOf(this, AssetError.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 * (PR 5) for container Lambdas; the parallel `AssetError` covers the\n * `cdkd publish-assets` / `cdkd deploy` build path. Kept distinct from\n * `AssetError` so `cdkd local invoke` failures don't show up under the\n * \"asset\" error class.\n */\nexport class LocalInvokeBuildError extends CdkdError {\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 * Resource provisioning errors\n */\nexport class ProvisioningError extends CdkdError {\n public readonly resourceType: string;\n public readonly logicalId: string;\n public readonly physicalId: string | undefined;\n\n constructor(\n message: string,\n resourceType: string,\n logicalId: string,\n physicalId?: string,\n cause?: Error\n ) {\n super(message, 'PROVISIONING_ERROR', cause);\n this.resourceType = resourceType;\n this.logicalId = logicalId;\n this.physicalId = physicalId;\n this.name = 'ProvisioningError';\n Object.setPrototypeOf(this, ProvisioningError.prototype);\n }\n}\n\n/**\n * Resource provisioning timeout errors (per-resource wall-clock deadline).\n *\n * Thrown by `withResourceDeadline` when a single CREATE / UPDATE / DELETE\n * operation exceeds the user-configured `--resource-timeout`. The deploy\n * engine catches this, wraps it in {@link ProvisioningError}, and lets the\n * existing failure path (interrupt siblings → pre-rollback save → rollback\n * unless `--no-rollback`) take over.\n *\n * The message intentionally names the resource, type, region, elapsed time\n * and operation, plus how to override the default. Long-running providers\n * (e.g. Custom Resource: 1h polling cap) self-report their needed budget\n * via `getMinResourceTimeoutMs()`, so the user only needs a per-type\n * override (`--resource-timeout TYPE=DURATION`) when they want to bump a\n * specific non-self-reporting type or shorten a self-reported one.\n */\nexport class ResourceTimeoutError extends CdkdError {\n public readonly logicalId: string;\n public readonly resourceType: string;\n public readonly region: string;\n public readonly elapsedMs: number;\n public readonly operation: 'CREATE' | 'UPDATE' | 'DELETE';\n public readonly timeoutMs: number;\n\n constructor(\n logicalId: string,\n resourceType: string,\n region: string,\n elapsedMs: number,\n operation: 'CREATE' | 'UPDATE' | 'DELETE',\n timeoutMs: number\n ) {\n const elapsedLabel = formatDuration(elapsedMs);\n const timeoutLabel = formatDuration(timeoutMs);\n super(\n `Resource ${logicalId} (${resourceType}) in ${region} timed out after ${timeoutLabel} during ${operation} (elapsed ${elapsedLabel}).\\n` +\n 'This may indicate a stuck Cloud Control polling loop, hung Custom Resource, or\\n' +\n `slow ENI provisioning. Re-run with --resource-timeout ${resourceType}=<DURATION>\\n` +\n 'to bump the budget for this resource type only, or --verbose to see the\\n' +\n 'underlying provider activity.',\n 'RESOURCE_TIMEOUT'\n );\n this.logicalId = logicalId;\n this.resourceType = resourceType;\n this.region = region;\n this.elapsedMs = elapsedMs;\n this.operation = operation;\n this.timeoutMs = timeoutMs;\n this.name = 'ResourceTimeoutError';\n Object.setPrototypeOf(this, ResourceTimeoutError.prototype);\n }\n}\n\n/**\n * Format a duration in milliseconds as a short human-readable label\n * (`30m`, `1h30m`, `45s`). Used by {@link ResourceTimeoutError} so the\n * error message stays compact.\n */\nfunction formatDuration(ms: number): string {\n if (ms < 60_000) {\n return `${Math.round(ms / 1000)}s`;\n }\n const totalMinutes = Math.round(ms / 60_000);\n if (totalMinutes < 60) return `${totalMinutes}m`;\n const hours = Math.floor(totalMinutes / 60);\n const minutes = totalMinutes % 60;\n return minutes === 0 ? `${hours}h` : `${hours}h${minutes}m`;\n}\n\n/**\n * Dependency resolution errors\n */\nexport class DependencyError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'DEPENDENCY_ERROR', cause);\n this.name = 'DependencyError';\n Object.setPrototypeOf(this, DependencyError.prototype);\n }\n}\n\n/**\n * Configuration errors\n */\nexport class ConfigError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'CONFIG_ERROR', cause);\n this.name = 'ConfigError';\n Object.setPrototypeOf(this, ConfigError.prototype);\n }\n}\n\n/**\n * Signals a partial-failure outcome that should map to exit code 2 (not 1).\n *\n * Used by `cdkd destroy` and `cdkd state destroy` when one or more\n * per-resource deletes failed but the overall command finished its work\n * (state.json is preserved, the rest of the stack was deleted, and the\n * user can re-run to clean up the remaining resources).\n *\n * Exit code conventions:\n * - 0: command completed successfully, no resources left in error state.\n * - 1: command-level failure (auth error, bad arguments, synth crash,\n * unhandled exception). Default for any thrown error.\n * - 2: partial failure — work completed but some resources are still in\n * an error state. Re-running typically resolves it. Documented in\n * README's \"Exit codes\" section.\n *\n * `handleError` recognizes this class via `instanceof` and uses its\n * `exitCode` instead of the default 1.\n */\nexport class PartialFailureError extends CdkdError {\n readonly exitCode: number = 2;\n\n constructor(message: string, cause?: Error) {\n super(message, 'PARTIAL_FAILURE', cause);\n this.name = 'PartialFailureError';\n Object.setPrototypeOf(this, PartialFailureError.prototype);\n }\n}\n\n/**\n * Signals that a provider cannot perform an in-place `update` for a\n * resource type — most commonly because the AWS resource is structurally\n * immutable (`AWS::Lambda::LayerVersion`, `AWS::S3Tables::TableBucket` once\n * created, certain `AWS::EC2::*` sub-resources) or because the provider\n * surfaces a sub-resource attachment whose only mutation pattern is\n * delete + add (Lambda permission statements, IAM policy attachments).\n *\n * Surfaced through `cdkd drift --revert`, which calls\n * `provider.update(logicalId, physicalId, type, stateProps, awsProps)` to\n * push cdkd state values back into AWS for every drifted resource. When a\n * provider throws this error, the drift command collects it as a\n * per-resource outcome distinct from a generic AWS update failure: the\n * fix is to re-deploy with `--replace` (or recreate the resource), not to\n * retry the update.\n *\n * Carries the same `exitCode = 2` as {@link PartialFailureError} so a\n * drift run that hits one immutable resource is reported as partial-\n * success rather than fatal — the rest of the drifted resources still\n * had their `update` invoked, and the user has a clear next step printed\n * for the unsupported one.\n */\nexport class ResourceUpdateNotSupportedError extends CdkdError {\n readonly exitCode: number = 2;\n public readonly resourceType: string;\n public readonly logicalId: string;\n /**\n * Human-readable hint printed alongside the error. The default is\n * \"use cdkd deploy with --replace, or change the resource definition\n * to create a new version\" — providers are encouraged to override\n * with a more specific suggestion when one is available (e.g.\n * Lambda::Permission's \"delete + add a new statement\").\n */\n public readonly suggestion: string | undefined;\n\n constructor(resourceType: string, logicalId: string, suggestion?: string, cause?: Error) {\n const tail = suggestion\n ? suggestion\n : 'use cdkd deploy with --replace, or change the resource definition to create a new version';\n super(\n `${resourceType} (${logicalId}) cannot be updated in place: ${tail}.`,\n 'RESOURCE_UPDATE_NOT_SUPPORTED',\n cause\n );\n this.resourceType = resourceType;\n this.logicalId = logicalId;\n this.suggestion = suggestion;\n this.name = 'ResourceUpdateNotSupportedError';\n Object.setPrototypeOf(this, ResourceUpdateNotSupportedError.prototype);\n }\n}\n\n/**\n * Signals a refusal to destroy a stack whose CDK manifest has\n * `terminationProtection: true`.\n *\n * Surfaced from `cdkd destroy <stack>` / `cdkd destroy --all` BEFORE\n * any lock acquisition or per-resource delete. In multi-stack runs\n * (e.g. `--all`) this counts as a per-stack failure and the rest of\n * the targets continue — the aggregated count is wrapped in\n * {@link PartialFailureError} so the command exits with code 2.\n *\n * The bypass workflow is documented in the message: edit the CDK code\n * (`new Stack(app, '...', { terminationProtection: false })`),\n * redeploy, then retry the destroy. A future `--remove-protection`\n * flag (separate scope) will provide an explicit one-shot bypass.\n *\n * Note: `cdkd state destroy` (state-only, no synth) does NOT honor\n * `terminationProtection` — the flag is a CDK property not persisted\n * in cdkd's state.json. Use `cdkd destroy` when synth is available.\n */\nexport class StackTerminationProtectionError extends CdkdError {\n public readonly stackName: string;\n\n constructor(stackName: string, cause?: Error) {\n super(\n `Stack '${stackName}' has terminationProtection: true and cannot be destroyed. ` +\n `Set terminationProtection: false in the CDK code, redeploy, then retry 'cdkd destroy ${stackName}'.`,\n 'STACK_TERMINATION_PROTECTION',\n cause\n );\n this.stackName = stackName;\n this.name = 'StackTerminationProtectionError';\n Object.setPrototypeOf(this, StackTerminationProtectionError.prototype);\n }\n}\n\n/**\n * One consumer that still references the producer being destroyed via\n * `Fn::ImportValue`. Surfaced inside {@link StackHasActiveImportsError}.\n */\nexport interface ActiveImportConsumer {\n consumerStack: string;\n consumerRegion: string;\n exportName: string;\n}\n\n/**\n * `cdkd destroy <producer>` refused because at least one consumer stack\n * still records an `Fn::ImportValue` reference to one of the producer's\n * outputs. This matches CloudFormation's strong-reference semantics —\n * CFn rejects `DeleteStack` for an exporter while an importer exists.\n *\n * cdkd has no `--force` escape hatch for this (intentionally, mirroring\n * CFn). The error message lists every offending consumer and points the\n * user at the two valid resolution paths:\n *\n * 1. Destroy the consumer first: `cdkd destroy <consumer>`\n * 2. Remove the `Fn::ImportValue` from the consumer's template and\n * redeploy, then retry the producer destroy.\n *\n * Weak-reference consumers (`Fn::GetStackOutput`, cdkd-specific) never\n * trigger this error by design — the producer stays deletable\n * independently of consumers when the user intentionally chose a weak\n * reference at template-authoring time.\n *\n * Exit code 2 (same as `PartialFailureError`) so multi-stack `cdkd\n * destroy --all` runs that partially succeed still surface as\n * non-zero without being indistinguishable from a fatal cdkd error.\n */\nexport class StackHasActiveImportsError extends CdkdError {\n readonly exitCode: number = 2;\n public readonly producerStack: string;\n public readonly producerRegion: string;\n public readonly consumers: ActiveImportConsumer[];\n\n constructor(\n producerStack: string,\n producerRegion: string,\n consumers: ActiveImportConsumer[],\n cause?: Error\n ) {\n const lines = consumers.map(\n (c) => ` - ${c.consumerStack} (${c.consumerRegion}): imports export '${c.exportName}'`\n );\n super(\n `Cannot destroy stack '${producerStack}' (${producerRegion}): ` +\n `the following stacks still import its outputs via Fn::ImportValue:\\n` +\n `${lines.join('\\n')}\\n\\n` +\n `This matches CloudFormation's strong-reference semantics — exports are\\n` +\n `protected as long as a consumer references them.\\n\\n` +\n `To proceed:\\n` +\n ` 1. Destroy the consumer first: cdkd destroy <consumer-stack>\\n` +\n ` 2. Or remove the Fn::ImportValue from the consumer's template\\n` +\n ` (e.g. inline the value, or refactor) and re-deploy the consumer,\\n` +\n ` then retry this destroy.\\n\\n` +\n `Note: cdkd's Fn::GetStackOutput intrinsic is a weak alternative that\\n` +\n `does NOT protect the producer — use it when you intentionally want\\n` +\n `the producer to be deletable independently of consumers.`,\n 'STACK_HAS_ACTIVE_IMPORTS',\n cause\n );\n this.producerStack = producerStack;\n this.producerRegion = producerRegion;\n this.consumers = consumers;\n this.name = 'StackHasActiveImportsError';\n Object.setPrototypeOf(this, StackHasActiveImportsError.prototype);\n }\n}\n\n/**\n * Signals that `cdkd local 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\n * `AuthType !== 'NONE'` or `InvokeMode === 'RESPONSE_STREAM'`, or an\n * unsupported intrinsic function in `IntegrationUri`.\n *\n * The message names every offending route and points the user at the\n * deferred follow-up PR (8b for authorizers, etc.). Hard-error at\n * discovery so the server never starts in a half-working state.\n */\nexport class RouteDiscoveryError extends CdkdError {\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 an unrecoverable failure inside `cdkd local start-api`'s HTTP\n * server — port-binding failure, RIE returned malformed JSON, container\n * pool acquire timed out, etc. Distinct from {@link RouteDiscoveryError}\n * which fires before the server starts.\n */\nexport class StartApiServerError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'START_API_SERVER_ERROR', cause);\n this.name = 'StartApiServerError';\n Object.setPrototypeOf(this, StartApiServerError.prototype);\n }\n}\n\n/**\n * Signals a `cdkd local run-task` orchestration failure that did not\n * originate from a lower-level module (those throw their own narrower\n * errors — `EcsTaskResolutionError`, `EcsSecretsResolutionError`,\n * `DockerRunnerError`, `LocalInvokeBuildError`). Used by the runner /\n * CLI when the failure is meaningful only at the task-orchestrator\n * layer (e.g. cyclic dependsOn, essential container did not start).\n */\nexport class LocalRunTaskError extends CdkdError {\n constructor(message: string, cause?: Error) {\n super(message, 'LOCAL_RUN_TASK_ERROR', cause);\n this.name = 'LocalRunTaskError';\n Object.setPrototypeOf(this, LocalRunTaskError.prototype);\n }\n}\n\n/**\n * Check if error is a cdkd error\n */\nexport function isCdkdError(error: unknown): error is CdkdError {\n return error instanceof CdkdError;\n}\n\n/**\n * Format error for display\n */\nexport function formatError(error: unknown): string {\n if (isCdkdError(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). `PartialFailureError`\n * overrides it to 2 so callers can distinguish \"command crashed /\n * unauthorized / bad arguments\" from \"command completed but some\n * resources are still in an error state, re-run to clean up\".\n *\n * A {@link CdkdError} subclass may set `silent = true` to suppress the\n * default `logger.error` line — used by `cdkd drift` where the command\n * has already printed a richer report and only needs the exit code.\n */\nexport function handleError(error: unknown): never {\n const logger = getLogger();\n const silent = error instanceof CdkdError && (error as CdkdError & { 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 const exitCode = error instanceof PartialFailureError ? error.exitCode : 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 `cdkd 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 { GetBucketLocationCommand, S3Client } from '@aws-sdk/client-s3';\n\n/**\n * Per-bucket region cache.\n *\n * Storing the in-flight `Promise` (rather than the resolved value) collapses\n * concurrent calls for the same bucket into a single `GetBucketLocation`\n * request — the second caller awaits the same promise instead of issuing a\n * duplicate API call.\n */\nconst cache = new Map<string, Promise<string>>();\n\n/**\n * Options accepted by {@link resolveBucketRegion}.\n *\n * `profile` and `credentials` mirror the AWS SDK shape so callers can pass\n * the same auth configuration the rest of cdkd uses.\n *\n * `fallbackRegion` is returned if `GetBucketLocation` fails for any reason —\n * the resolver never throws so a missing/forbidden bucket does not block the\n * caller from surfacing a more useful downstream error (e.g. the actionable\n * `normalizeAwsError` message).\n */\nexport interface ResolveBucketRegionOptions {\n profile?: string;\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n };\n fallbackRegion?: string;\n}\n\n/**\n * Resolve the AWS region of an S3 bucket via `GetBucketLocation`.\n *\n * Why `GetBucketLocation` rather than `HeadBucket`:\n * AWS SDK v3's region-redirect middleware does not handle the empty-body\n * HEAD response on a 301 cross-region redirect cleanly — the protocol\n * parser falls through to `getErrorSchemaOrThrowBaseException` and\n * produces a synthetic `name: 'Unknown', message: 'UnknownError'`.\n * `GetBucketLocation` is a GET with an XML body and is not subject to the\n * same SDK glitch.\n *\n * Why a region-agnostic client (us-east-1):\n * `GetBucketLocation` works against the global S3 endpoint regardless of\n * the bucket's actual region, so we don't need to know the answer to ask\n * the question.\n *\n * The result is cached per bucket name for the process lifetime — bucket\n * regions never move, so the cache never needs invalidation.\n *\n * @returns The bucket's region (e.g. `us-west-2`). An empty `LocationConstraint`\n * in the response means `us-east-1` (S3 quirk). On any error, returns\n * `opts.fallbackRegion` if provided, else `us-east-1`.\n */\nexport async function resolveBucketRegion(\n bucketName: string,\n opts: ResolveBucketRegionOptions = {}\n): Promise<string> {\n const cached = cache.get(bucketName);\n if (cached) return cached;\n\n const promise = (async (): Promise<string> => {\n const client = new S3Client({\n region: 'us-east-1',\n ...(opts.profile && { profile: opts.profile }),\n ...(opts.credentials && { credentials: opts.credentials }),\n });\n try {\n const response = await client.send(new GetBucketLocationCommand({ Bucket: bucketName }));\n // Empty / null `LocationConstraint` is S3's way of saying us-east-1.\n return response.LocationConstraint || 'us-east-1';\n } catch {\n // The resolver never throws: cdkd would rather surface the actionable\n // downstream error (HeadBucket → `normalizeAwsError`) than mask it\n // behind a noisy GetBucketLocation failure.\n return opts.fallbackRegion ?? 'us-east-1';\n } finally {\n client.destroy();\n }\n })();\n\n cache.set(bucketName, promise);\n return promise;\n}\n\n/**\n * Clear the per-bucket region cache. Used by tests to reset state between\n * cases — production code never needs to call this.\n */\nexport function clearBucketRegionCache(): void {\n cache.clear();\n}\n","import { spawn } from 'node:child_process';\nimport { writeFileSync, mkdtempSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { getLogger } from '../utils/logger.js';\nimport { SynthesisError } from '../utils/error-handler.js';\n\n/**\n * Options for CDK app execution\n */\nexport interface AppExecutorOptions {\n /** CDK app command (e.g., \"node app.ts\") */\n app: string;\n\n /** Output directory for cloud assembly (default: \"cdk.out\") */\n outputDir: string;\n\n /** Context key-value pairs to pass to the app */\n context: Record<string, unknown>;\n\n /** AWS region */\n region?: string;\n\n /** AWS account ID */\n accountId?: string;\n}\n\n/** Cloud assembly schema version compatible with CDK v2 */\nconst CDK_ASM_VERSION = '38.0.0';\n\n/** Maximum context size before overflow to temp file (32KB) */\nconst CONTEXT_OVERFLOW_LIMIT = 32 * 1024;\n\n/**\n * Executes CDK app as subprocess to produce a cloud assembly\n */\nexport class AppExecutor {\n private logger = getLogger().child('AppExecutor');\n\n /**\n * Execute CDK app and produce cloud assembly in outputDir\n */\n async execute(options: AppExecutorOptions): Promise<void> {\n const { app, outputDir, context, region, accountId } = options;\n\n this.logger.debug('Executing CDK app:', app);\n this.logger.debug('Output directory:', outputDir);\n\n // Build environment variables\n const env: Record<string, string> = {\n ...process.env,\n CDK_OUTDIR: outputDir,\n };\n\n if (region) {\n env['CDK_DEFAULT_REGION'] = region;\n }\n if (accountId) {\n env['CDK_DEFAULT_ACCOUNT'] = accountId;\n }\n\n // Cloud assembly version and CLI version for compatibility\n env['CDK_CLI_ASM_VERSION'] = CDK_ASM_VERSION;\n env['CDK_CLI_VERSION'] = '2.1000.0';\n\n // Pass context via environment variable or temp file\n let contextTempDir: string | undefined;\n const contextJson = JSON.stringify(context);\n\n if (Buffer.byteLength(contextJson, 'utf-8') > CONTEXT_OVERFLOW_LIMIT) {\n // Context too large: write to temp file\n contextTempDir = mkdtempSync(join(tmpdir(), 'cdkd-context-'));\n const contextFile = join(contextTempDir, 'context.json');\n writeFileSync(contextFile, contextJson, 'utf-8');\n env['CONTEXT_OVERFLOW_LOCATION_ENV'] = contextFile;\n this.logger.debug('Context overflow: written to temp file');\n } else {\n env['CDK_CONTEXT_JSON'] = contextJson;\n }\n\n // Determine executable\n const commandLine = this.guessExecutable(app);\n this.logger.debug('Command line:', commandLine);\n\n try {\n await this.spawn(commandLine, env);\n this.logger.debug('CDK app execution completed');\n } finally {\n // Clean up temp context file\n if (contextTempDir) {\n try {\n rmSync(contextTempDir, { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n }\n }\n\n /**\n * Determine how to execute the app command\n * - If it's a .js file, prepend node\n * - Otherwise execute as shell command\n */\n private guessExecutable(app: string): string {\n const trimmed = app.trim();\n\n // If it ends with .js, prepend the current node executable\n if (trimmed.endsWith('.js') || trimmed.split(/\\s+/)[0]?.endsWith('.js')) {\n const parts = trimmed.split(/\\s+/);\n parts[0] = `\"${process.execPath}\" \"${parts[0]}\"`;\n return parts.join(' ');\n }\n\n return trimmed;\n }\n\n /**\n * Spawn subprocess and wait for completion\n */\n private spawn(commandLine: string, env: Record<string, string>): Promise<void> {\n return new Promise((resolve, reject) => {\n const proc = spawn(commandLine, {\n stdio: ['ignore', 'pipe', 'pipe'],\n shell: true,\n env,\n cwd: process.cwd(),\n });\n\n const stderrChunks: string[] = [];\n\n proc.stdout?.on('data', (data: Buffer) => {\n const line = data.toString().trim();\n if (line) {\n this.logger.debug('[app stdout]', line);\n }\n });\n\n proc.stderr?.on('data', (data: Buffer) => {\n const line = data.toString().trim();\n if (line) {\n stderrChunks.push(line);\n // CDK bundling progress and warnings come through stderr\n this.logger.info(line);\n }\n });\n\n proc.on('error', (error) => {\n reject(new SynthesisError(`Failed to execute CDK app: ${error.message}`, error));\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n const stderr = stderrChunks.join('\\n');\n reject(\n new SynthesisError(\n `CDK app exited with code ${code}${stderr ? `\\n\\nstderr:\\n${stderr}` : ''}`\n )\n );\n }\n });\n });\n }\n}\n","/**\n * Cloud Assembly types\n *\n * Based on CDK Cloud Assembly manifest format.\n * These types replace @aws-cdk/cloud-assembly-api dependency.\n */\n\n/**\n * Cloud Assembly manifest (manifest.json)\n */\nexport interface AssemblyManifest {\n /** Cloud assembly schema version */\n version: string;\n\n /** Artifacts in the assembly */\n artifacts?: Record<string, ArtifactManifest>;\n\n /** Missing context values that need to be resolved */\n missing?: MissingContext[];\n\n /** Runtime information */\n runtime?: RuntimeInfo;\n}\n\n/**\n * Artifact manifest entry\n */\nexport interface ArtifactManifest {\n /** Artifact type */\n type: ArtifactType;\n\n /** Target environment (e.g., \"aws://123456789012/us-east-1\") */\n environment?: string;\n\n /**\n * Hierarchical display name (e.g., \"MyStage/MyStack\" for stacks under a Stage,\n * or just \"MyStack\" at the top level). Set by CDK synth.\n */\n displayName?: string;\n\n /** Artifact-specific properties */\n properties?: Record<string, unknown>;\n\n /** Dependencies on other artifacts (by artifact ID) */\n dependencies?: string[];\n\n /** Metadata entries */\n metadata?: Record<string, MetadataEntry[]>;\n}\n\n/**\n * Artifact types\n */\nexport type ArtifactType =\n | 'aws:cloudformation:stack'\n | 'cdk:asset-manifest'\n | 'cdk:tree'\n | 'cdk:cloud-assembly'\n | 'cdk:feature-flag-report';\n\n/**\n * CloudFormation stack artifact properties\n */\nexport interface StackArtifactProperties {\n /** Path to template file relative to assembly directory */\n templateFile: string;\n\n /** Physical stack name */\n stackName?: string;\n\n /** Stack parameters */\n parameters?: Record<string, string>;\n\n /** Stack tags */\n tags?: Record<string, string>;\n\n /** Role to assume for deployment */\n assumeRoleArn?: string;\n\n /** CloudFormation execution role */\n cloudFormationExecutionRoleArn?: string;\n\n /** Termination protection */\n terminationProtection?: boolean;\n}\n\n/**\n * Asset manifest artifact properties\n */\nexport interface AssetManifestArtifactProperties {\n /** Path to asset manifest file relative to assembly directory */\n file: string;\n\n /** Required bootstrap stack version */\n requiresBootstrapStackVersion?: number;\n}\n\n/**\n * Missing context entry\n */\nexport interface MissingContext {\n /** Context key */\n key: string;\n\n /** Context provider type */\n provider: string;\n\n /** Provider-specific query properties */\n props: ContextQueryProperties;\n}\n\n/**\n * Base context query properties (all providers extend this)\n */\nexport interface ContextQueryProperties {\n /** Target AWS account */\n account: string;\n\n /** Target AWS region */\n region: string;\n\n /** Role to assume for lookup */\n lookupRoleArn?: string;\n\n /** Additional properties (provider-specific) */\n [key: string]: unknown;\n}\n\n/**\n * Metadata entry in artifact\n */\nexport interface MetadataEntry {\n type: string;\n data?: unknown;\n trace?: string[];\n}\n\n/**\n * Runtime information\n */\nexport interface RuntimeInfo {\n libraries?: Record<string, string>;\n}\n\n/**\n * Parsed environment from artifact\n */\nexport interface ArtifactEnvironment {\n account: string;\n region: string;\n}\n\n/**\n * Parse environment string \"aws://account/region\"\n */\nexport function parseEnvironment(env: string): ArtifactEnvironment {\n const match = env.match(/^aws:\\/\\/([^/]+)\\/(.+)$/);\n if (!match) {\n return { account: 'unknown-account', region: 'unknown-region' };\n }\n return {\n account: match[1] === 'unknown-account' ? 'unknown-account' : match[1]!,\n region: match[2] === 'unknown-region' ? 'unknown-region' : match[2]!,\n };\n}\n","import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type {\n AssemblyManifest,\n ArtifactManifest,\n StackArtifactProperties,\n AssetManifestArtifactProperties,\n ArtifactEnvironment,\n} from '../types/assembly.js';\nimport { parseEnvironment } from '../types/assembly.js';\nimport type { CloudFormationTemplate } from '../types/resource.js';\nimport { getLogger } from '../utils/logger.js';\nimport { SynthesisError } from '../utils/error-handler.js';\n\n/**\n * Stack information extracted from cloud assembly\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\" for stacks\n * under a Stage, or \"MyStack\" at the top level). Falls back to `stackName` when\n * the assembly does not carry one.\n */\n displayName: string;\n\n /** Artifact ID in manifest */\n artifactId: string;\n\n /** CloudFormation template */\n template: CloudFormationTemplate;\n\n /** Asset manifest file path (absolute) */\n assetManifestPath?: string | undefined;\n\n /** Stack dependency names (other stacks this stack depends on) */\n dependencyNames: string[];\n\n /** Target region from CDK environment */\n region?: string | undefined;\n\n /** Target account from CDK environment */\n account?: string | undefined;\n\n /**\n * Stack-level termination protection (CDK `Stack.terminationProtection`).\n *\n * When `true`, `cdkd destroy <stack>` and `cdkd destroy --all` refuse to\n * destroy this stack and surface a `StackTerminationProtectionError` so\n * the CLI exits via the partial-failure path (exit 2) without invoking\n * the per-resource delete loop. The bypass workflow is to set\n * `terminationProtection: false` in the CDK code, redeploy, then retry.\n *\n * Read-only `cdkd diff` and forward-only `cdkd deploy` are unaffected.\n * `cdkd state destroy` (state-only, no synth) cannot honor this — the\n * flag is a CDK property not stored in cdkd state.json.\n */\n terminationProtection?: boolean | undefined;\n}\n\n/**\n * Reads and parses Cloud Assembly from cdk.out directory\n */\nexport class AssemblyReader {\n private logger = getLogger().child('AssemblyReader');\n\n /**\n * Read manifest.json from assembly directory\n */\n readManifest(assemblyDir: string): AssemblyManifest {\n const manifestPath = join(assemblyDir, 'manifest.json');\n\n try {\n const content = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as AssemblyManifest;\n this.logger.debug(`Loaded manifest: version=${manifest.version}`);\n return manifest;\n } catch (error) {\n throw new SynthesisError(\n `Failed to read cloud assembly manifest from ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get all stacks from assembly (recursively traverses nested assemblies / Stages)\n */\n getAllStacks(assemblyDir: string, manifest: AssemblyManifest): StackInfo[] {\n if (!manifest.artifacts) {\n this.logger.warn('No artifacts found in manifest');\n return [];\n }\n\n // Build map of artifact ID → asset manifest path\n const assetManifestMap = this.buildAssetManifestMap(assemblyDir, manifest);\n\n const stacks: StackInfo[] = [];\n\n for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) {\n if (artifact.type === 'aws:cloudformation:stack') {\n const stackInfo = this.extractStackInfo(\n assemblyDir,\n artifactId,\n artifact,\n manifest,\n assetManifestMap\n );\n stacks.push(stackInfo);\n } else if (artifact.type === 'cdk:cloud-assembly') {\n // Nested assembly (Stage) — recurse into subdirectory\n const props = artifact.properties as { directoryName?: string } | undefined;\n if (props?.directoryName) {\n const nestedDir = join(assemblyDir, props.directoryName);\n try {\n const nestedManifest = this.readManifest(nestedDir);\n const nestedStacks = this.getAllStacks(nestedDir, nestedManifest);\n stacks.push(...nestedStacks);\n } catch (error) {\n this.logger.warn(\n `Failed to read nested assembly '${props.directoryName}': ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n }\n }\n\n this.logger.debug(`Found ${stacks.length} stack(s) in assembly`);\n return stacks;\n }\n\n /**\n * Get a specific stack by name\n */\n getStack(assemblyDir: string, manifest: AssemblyManifest, stackName: string): StackInfo {\n const stacks = this.getAllStacks(assemblyDir, manifest);\n const stack = stacks.find((s) => s.stackName === stackName);\n\n if (!stack) {\n throw new SynthesisError(\n `Stack '${stackName}' not found in assembly. Available: ${stacks.map((s) => s.stackName).join(', ')}`\n );\n }\n\n return stack;\n }\n\n /**\n * Get template for a specific stack\n */\n getTemplate(\n assemblyDir: string,\n manifest: AssemblyManifest,\n stackName: string\n ): CloudFormationTemplate {\n return this.getStack(assemblyDir, manifest, stackName).template;\n }\n\n /**\n * Build map: stack artifact ID → asset manifest absolute path\n */\n private buildAssetManifestMap(\n assemblyDir: string,\n manifest: AssemblyManifest\n ): Map<string, string> {\n const map = new Map<string, string>();\n\n if (!manifest.artifacts) return map;\n\n for (const [artifactId, artifact] of Object.entries(manifest.artifacts)) {\n if (artifact.type !== 'cdk:asset-manifest') continue;\n\n const props = artifact.properties as AssetManifestArtifactProperties | undefined;\n if (props?.file) {\n map.set(artifactId, join(assemblyDir, props.file));\n }\n }\n\n return map;\n }\n\n /**\n * Extract stack info from artifact\n */\n private extractStackInfo(\n assemblyDir: string,\n artifactId: string,\n artifact: ArtifactManifest,\n manifest: AssemblyManifest,\n assetManifestMap: Map<string, string>\n ): StackInfo {\n const props = artifact.properties as StackArtifactProperties | undefined;\n const stackName = props?.stackName || artifactId;\n\n // Load template\n const templateFile = props?.templateFile;\n if (!templateFile) {\n throw new SynthesisError(`Stack '${stackName}' has no templateFile property`);\n }\n\n const templatePath = join(assemblyDir, templateFile);\n let template: CloudFormationTemplate;\n try {\n const content = readFileSync(templatePath, 'utf-8');\n template = JSON.parse(content) as CloudFormationTemplate;\n } catch (error) {\n throw new SynthesisError(\n `Failed to read template for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n this.logger.debug(\n `Stack: ${stackName}, Resources: ${Object.keys(template.Resources ?? {}).length}`\n );\n\n // Find asset manifest for this stack\n let assetManifestPath: string | undefined;\n if (artifact.dependencies) {\n for (const depId of artifact.dependencies) {\n if (assetManifestMap.has(depId)) {\n assetManifestPath = assetManifestMap.get(depId);\n this.logger.debug(`Found asset manifest for ${stackName}: ${depId}`);\n break;\n }\n }\n }\n\n // Extract stack dependencies (other stacks, not asset manifests)\n const dependencyNames: string[] = [];\n if (artifact.dependencies && manifest.artifacts) {\n for (const depId of artifact.dependencies) {\n const depArtifact = manifest.artifacts[depId];\n if (depArtifact?.type === 'aws:cloudformation:stack') {\n const depProps = depArtifact.properties as StackArtifactProperties | undefined;\n const depName = depProps?.stackName || depId;\n if (depName !== stackName) {\n dependencyNames.push(depName);\n }\n }\n }\n }\n\n if (dependencyNames.length > 0) {\n this.logger.debug(`Stack '${stackName}' depends on: [${dependencyNames.join(', ')}]`);\n }\n\n // Parse environment\n let env: ArtifactEnvironment | undefined;\n if (artifact.environment) {\n env = parseEnvironment(artifact.environment);\n }\n\n return {\n stackName,\n displayName: artifact.displayName ?? stackName,\n artifactId,\n template,\n assetManifestPath,\n dependencyNames,\n region: env?.region !== 'unknown-region' ? env?.region : undefined,\n account: env?.account !== 'unknown-account' ? env?.account : undefined,\n ...(props?.terminationProtection !== undefined && {\n terminationProtection: props.terminationProtection,\n }),\n };\n }\n\n /**\n * Check if stack has assets\n */\n hasAssets(stackInfo: StackInfo): boolean {\n return stackInfo.assetManifestPath !== undefined;\n }\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { getLogger } from '../utils/logger.js';\n\nconst CDK_CONTEXT_FILE = 'cdk.context.json';\n\n/**\n * Manages reading and writing of cdk.context.json\n *\n * Context values resolved by context providers are persisted here\n * so they don't need to be re-fetched on subsequent synthesis runs.\n * Format is compatible with CDK CLI.\n */\nexport class ContextStore {\n private logger = getLogger().child('ContextStore');\n\n /**\n * Load context values from cdk.context.json\n *\n * @param cwd Working directory (default: process.cwd())\n * @returns Context key-value map, or empty object if file doesn't exist\n */\n load(cwd?: string): Record<string, unknown> {\n const filePath = resolve(cwd || process.cwd(), CDK_CONTEXT_FILE);\n\n if (!existsSync(filePath)) {\n this.logger.debug('No cdk.context.json found');\n return {};\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const context = JSON.parse(content) as Record<string, unknown>;\n this.logger.debug(\n `Loaded ${Object.keys(context).length} context value(s) from cdk.context.json`\n );\n return context;\n } catch (error) {\n this.logger.warn(\n `Failed to parse cdk.context.json: ${error instanceof Error ? error.message : String(error)}`\n );\n return {};\n }\n }\n\n /**\n * Save resolved context values to cdk.context.json\n *\n * Merges with existing values. Transient values (errors) are excluded.\n *\n * @param updates Key-value pairs to save\n * @param cwd Working directory (default: process.cwd())\n */\n save(updates: Record<string, unknown>, cwd?: string): void {\n const filePath = resolve(cwd || process.cwd(), CDK_CONTEXT_FILE);\n\n // Load existing values\n const existing = this.load(cwd);\n\n // Merge, excluding transient values (provider errors)\n for (const [key, value] of Object.entries(updates)) {\n if (this.isTransient(value)) {\n this.logger.debug(`Skipping transient context value for key: ${key}`);\n continue;\n }\n existing[key] = value;\n }\n\n // Write back\n writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\\n', 'utf-8');\n this.logger.debug(`Saved ${Object.keys(updates).length} context value(s) to cdk.context.json`);\n }\n\n /**\n * Check if a context value is transient (should not be persisted)\n *\n * CDK CLI marks provider errors with $dontSaveContext: true\n */\n private isTransient(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false;\n return (value as Record<string, unknown>)['$dontSaveContext'] === true;\n }\n}\n","import { EC2Client, DescribeAvailabilityZonesCommand } from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Availability Zones context provider\n *\n * Returns available AZ names for a region.\n * CDK provider type: \"availability-zones\"\n */\nexport class AZContextProvider implements ContextProvider {\n private logger = getLogger().child('AZContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<string[]> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n\n this.logger.debug(`Fetching availability zones for region: ${region}`);\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(new DescribeAvailabilityZonesCommand({}));\n\n const azs = (response.AvailabilityZones ?? [])\n .filter((az) => az.State === 'available')\n .map((az) => az.ZoneName!)\n .filter(Boolean)\n .sort();\n\n this.logger.debug(`Found ${azs.length} availability zones: ${azs.join(', ')}`);\n return azs;\n } finally {\n client.destroy();\n }\n }\n}\n","import { SSMClient, GetParameterCommand } from '@aws-sdk/client-ssm';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * SSM Parameter context provider\n *\n * Reads SSM parameter values.\n * CDK provider type: \"ssm\"\n */\nexport class SSMContextProvider implements ContextProvider {\n private logger = getLogger().child('SSMContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const parameterName = props['parameterName'] as string;\n\n if (!parameterName) {\n throw new Error('SSM context provider requires parameterName property');\n }\n\n this.logger.debug(`Reading SSM parameter: ${parameterName} (region: ${region})`);\n\n const client = new SSMClient({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(new GetParameterCommand({ Name: parameterName }));\n\n if (!response.Parameter || response.Parameter.Value === undefined) {\n // Check if we should suppress this error\n const suppressError = props['ignoreErrorOnMissingContext'] === true;\n if (suppressError && 'dummyValue' in props) {\n this.logger.debug(`SSM parameter not found, returning dummy value`);\n return props['dummyValue'];\n }\n throw new Error(`SSM parameter not found: ${parameterName}`);\n }\n\n this.logger.debug(`SSM parameter resolved: ${parameterName}`);\n return response.Parameter.Value;\n } finally {\n client.destroy();\n }\n }\n}\n","import {\n Route53Client,\n ListHostedZonesByNameCommand,\n GetHostedZoneCommand,\n} from '@aws-sdk/client-route-53';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Hosted Zone context provider\n *\n * Looks up Route53 hosted zones by domain name.\n * CDK provider type: \"hosted-zone\"\n */\nexport class HostedZoneContextProvider implements ContextProvider {\n private logger = getLogger().child('HostedZoneContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const domainName = props['domainName'] as string;\n const privateZone = props['privateZone'] as boolean | undefined;\n const vpcId = props['vpcId'] as string | undefined;\n\n if (!domainName) {\n throw new Error('Hosted zone context provider requires domainName property');\n }\n\n this.logger.debug(`Looking up hosted zone: ${domainName} (private: ${privateZone})`);\n\n const client = new Route53Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(\n new ListHostedZonesByNameCommand({\n DNSName: domainName,\n MaxItems: 10,\n })\n );\n\n const zones = response.HostedZones ?? [];\n\n // Filter by domain name (exact match with trailing dot)\n const normalizedDomain = domainName.endsWith('.') ? domainName : `${domainName}.`;\n const matching = zones.filter((z) => z.Name === normalizedDomain);\n\n // Filter by private/public\n let filtered = matching;\n if (privateZone !== undefined) {\n filtered = matching.filter((z) => z.Config?.PrivateZone === privateZone);\n }\n\n // Filter by VPC (for private zones)\n if (vpcId && filtered.length > 0) {\n const vpcFiltered = [];\n for (const zone of filtered) {\n const zoneDetail = await client.send(new GetHostedZoneCommand({ Id: zone.Id }));\n const zoneVpcs = zoneDetail.VPCs ?? [];\n if (zoneVpcs.some((v) => v.VPCId === vpcId)) {\n vpcFiltered.push(zone);\n }\n }\n filtered = vpcFiltered;\n }\n\n if (filtered.length === 0) {\n throw new Error(\n `No hosted zone found for domain: ${domainName}` +\n (privateZone !== undefined ? ` (private: ${privateZone})` : '') +\n (vpcId ? ` (vpcId: ${vpcId})` : '')\n );\n }\n\n if (filtered.length > 1) {\n throw new Error(\n `Multiple hosted zones found for domain: ${domainName}. ` +\n `Found: ${filtered.map((z) => z.Id).join(', ')}`\n );\n }\n\n const zone = filtered[0]!;\n // Strip /hostedzone/ prefix from ID\n const zoneId = zone.Id!.replace('/hostedzone/', '');\n\n this.logger.debug(`Resolved hosted zone: ${zoneId} (${zone.Name})`);\n\n return {\n Id: zoneId,\n Name: zone.Name,\n };\n } finally {\n client.destroy();\n }\n }\n}\n","import {\n EC2Client,\n DescribeVpcsCommand,\n DescribeSubnetsCommand,\n DescribeRouteTablesCommand,\n DescribeVpnGatewaysCommand,\n type Filter,\n type Subnet,\n} from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * VPC context provider\n *\n * Discovers VPC details including subnets, route tables, and AZs.\n * CDK provider type: \"vpc-provider\"\n */\nexport class VpcContextProvider implements ContextProvider {\n private logger = getLogger().child('VpcContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const filter = props['filter'] as Record<string, string> | undefined;\n const returnAsymmetricSubnets = props['returnAsymmetricSubnets'] as boolean | undefined;\n const subnetGroupNameTag = (props['subnetGroupNameTag'] as string) || 'aws-cdk:subnet-name';\n const returnVpnGateways = props['returnVpnGateways'] as boolean | undefined;\n\n this.logger.debug(`Looking up VPC (region: ${region}, filter: ${JSON.stringify(filter)})`);\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n // 1. Find VPC\n const vpcFilters: Filter[] = filter\n ? Object.entries(filter).map(([name, value]) => ({\n Name: name,\n Values: [String(value)],\n }))\n : [];\n\n const vpcsResponse = await client.send(new DescribeVpcsCommand({ Filters: vpcFilters }));\n\n const vpcs = vpcsResponse.Vpcs ?? [];\n if (vpcs.length === 0) {\n throw new Error(`No VPC found matching filter: ${JSON.stringify(filter)}`);\n }\n if (vpcs.length > 1) {\n throw new Error(\n `Multiple VPCs found matching filter: ${JSON.stringify(filter)}. ` +\n `Found: ${vpcs.map((v) => v.VpcId).join(', ')}`\n );\n }\n\n const vpc = vpcs[0]!;\n const vpcId = vpc.VpcId!;\n this.logger.debug(`Found VPC: ${vpcId}`);\n\n // 2. Get subnets\n const subnetsResponse = await client.send(\n new DescribeSubnetsCommand({\n Filters: [{ Name: 'vpc-id', Values: [vpcId] }],\n })\n );\n const subnets = subnetsResponse.Subnets ?? [];\n\n // 3. Get route tables\n const rtResponse = await client.send(\n new DescribeRouteTablesCommand({\n Filters: [{ Name: 'vpc-id', Values: [vpcId] }],\n })\n );\n const routeTables = rtResponse.RouteTables ?? [];\n\n // Build subnet → route table mapping\n const subnetRouteTableMap = new Map<string, string>();\n let mainRouteTableId: string | undefined;\n for (const rt of routeTables) {\n for (const assoc of rt.Associations ?? []) {\n if (assoc.Main) {\n mainRouteTableId = rt.RouteTableId;\n }\n if (assoc.SubnetId && rt.RouteTableId) {\n subnetRouteTableMap.set(assoc.SubnetId, rt.RouteTableId);\n }\n }\n }\n\n // 4. Classify subnets\n const routeTableInfos = routeTables.map((rt) => ({\n routeTableId: rt.RouteTableId ?? '',\n routes: (rt.Routes ?? []).map((r) => ({\n gatewayId: r.GatewayId,\n natGatewayId: r.NatGatewayId,\n })),\n }));\n\n const classifiedSubnets = this.classifySubnets(\n subnets,\n subnetRouteTableMap,\n mainRouteTableId,\n routeTableInfos,\n subnetGroupNameTag\n );\n\n // Sort by AZ for consistent ordering\n const sortByAz = (a: SubnetInfo, b: SubnetInfo) => a.az.localeCompare(b.az);\n\n const publicSubnets = classifiedSubnets.filter((s) => s.type === 'Public').sort(sortByAz);\n const privateSubnets = classifiedSubnets.filter((s) => s.type === 'Private').sort(sortByAz);\n const isolatedSubnets = classifiedSubnets.filter((s) => s.type === 'Isolated').sort(sortByAz);\n\n // 5. Get VPN gateway (optional)\n let vpnGatewayId: string | undefined;\n if (returnVpnGateways !== false) {\n const vpnResponse = await client.send(\n new DescribeVpnGatewaysCommand({\n Filters: [\n { Name: 'attachment.vpc-id', Values: [vpcId] },\n { Name: 'attachment.state', Values: ['attached'] },\n ],\n })\n );\n vpnGatewayId = vpnResponse.VpnGateways?.[0]?.VpnGatewayId;\n }\n\n // 6. Build result\n const azs = [...new Set(subnets.map((s) => s.AvailabilityZone!))].sort();\n\n const result: Record<string, unknown> = {\n vpcId,\n vpcCidrBlock: vpc.CidrBlock,\n ownerAccountId: vpc.OwnerId,\n availabilityZones: azs,\n publicSubnetIds: publicSubnets.map((s) => s.subnetId),\n publicSubnetNames: publicSubnets.map((s) => s.name),\n publicSubnetRouteTableIds: publicSubnets.map((s) => s.routeTableId),\n privateSubnetIds: privateSubnets.map((s) => s.subnetId),\n privateSubnetNames: privateSubnets.map((s) => s.name),\n privateSubnetRouteTableIds: privateSubnets.map((s) => s.routeTableId),\n isolatedSubnetIds: isolatedSubnets.map((s) => s.subnetId),\n isolatedSubnetNames: isolatedSubnets.map((s) => s.name),\n isolatedSubnetRouteTableIds: isolatedSubnets.map((s) => s.routeTableId),\n };\n\n if (vpnGatewayId) {\n result['vpnGatewayId'] = vpnGatewayId;\n }\n\n if (returnAsymmetricSubnets) {\n result['subnetGroups'] = this.buildSubnetGroups(classifiedSubnets);\n }\n\n this.logger.debug(\n `VPC ${vpcId}: ${publicSubnets.length} public, ${privateSubnets.length} private, ${isolatedSubnets.length} isolated subnets`\n );\n\n return result;\n } finally {\n client.destroy();\n }\n }\n\n /**\n * Classify subnets as Public, Private, or Isolated\n */\n private classifySubnets(\n subnets: Subnet[],\n subnetRouteTableMap: Map<string, string>,\n mainRouteTableId: string | undefined,\n routeTables: {\n routeTableId: string;\n routes: { gatewayId?: string | undefined; natGatewayId?: string | undefined }[];\n }[],\n subnetGroupNameTag: string\n ): SubnetInfo[] {\n // Build route table → has IGW/NAT mapping\n const rtHasIgw = new Map<string, boolean>();\n const rtHasNat = new Map<string, boolean>();\n for (const rt of routeTables) {\n const hasIgw = rt.routes.some((r) => r.gatewayId?.startsWith('igw-'));\n const hasNat = rt.routes.some((r) => r.natGatewayId?.startsWith('nat-'));\n rtHasIgw.set(rt.routeTableId, hasIgw);\n rtHasNat.set(rt.routeTableId, hasNat);\n }\n\n return subnets.map((subnet) => {\n const subnetId = subnet.SubnetId!;\n const az = subnet.AvailabilityZone!;\n const routeTableId = subnetRouteTableMap.get(subnetId) || mainRouteTableId || '';\n\n // Determine type from tags first\n const tags = subnet.Tags ?? [];\n const nameTag = tags.find((t) => t.Key === subnetGroupNameTag);\n let name = nameTag?.Value || '';\n\n // Determine type\n let type: 'Public' | 'Private' | 'Isolated';\n if (nameTag?.Value) {\n // Trust tag-based classification\n const lowerName = nameTag.Value.toLowerCase();\n if (lowerName.includes('public')) {\n type = 'Public';\n } else if (lowerName.includes('private')) {\n type = 'Private';\n } else if (lowerName.includes('isolated')) {\n type = 'Isolated';\n } else {\n // Fall back to route analysis\n type = this.classifyByRoute(routeTableId, rtHasIgw, rtHasNat, subnet);\n }\n } else {\n type = this.classifyByRoute(routeTableId, rtHasIgw, rtHasNat, subnet);\n name = type;\n }\n\n return { subnetId, az, routeTableId, type, name };\n });\n }\n\n private classifyByRoute(\n routeTableId: string,\n rtHasIgw: Map<string, boolean>,\n rtHasNat: Map<string, boolean>,\n subnet: Subnet\n ): 'Public' | 'Private' | 'Isolated' {\n if (rtHasIgw.get(routeTableId) || subnet.MapPublicIpOnLaunch) {\n return 'Public';\n }\n if (rtHasNat.get(routeTableId)) {\n return 'Private';\n }\n return 'Isolated';\n }\n\n /**\n * Build subnet groups for asymmetric subnet support\n */\n private buildSubnetGroups(subnets: SubnetInfo[]): unknown[] {\n const groups = new Map<string, SubnetInfo[]>();\n for (const subnet of subnets) {\n const key = `${subnet.type}/${subnet.name}`;\n const group = groups.get(key) ?? [];\n group.push(subnet);\n groups.set(key, group);\n }\n\n return Array.from(groups.entries()).map(([, groupSubnets]) => ({\n name: groupSubnets[0]!.name,\n type: groupSubnets[0]!.type,\n subnets: groupSubnets\n .sort((a, b) => a.az.localeCompare(b.az))\n .map((s) => ({\n subnetId: s.subnetId,\n availabilityZone: s.az,\n routeTableId: s.routeTableId,\n })),\n }));\n }\n}\n\ninterface SubnetInfo {\n subnetId: string;\n az: string;\n routeTableId: string;\n type: 'Public' | 'Private' | 'Isolated';\n name: string;\n}\n","import {\n CloudControlClient,\n GetResourceCommand,\n ListResourcesCommand,\n} from '@aws-sdk/client-cloudcontrol';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Cloud Control API context provider\n *\n * Generic provider that uses Cloud Control API to lookup any resource type.\n * CDK provider type: \"cc-api-provider\"\n *\n * Used by CDK for lookups like IAM Roles, ECR repositories, RDS instances, etc.\n */\nexport class CcApiContextProvider implements ContextProvider {\n private logger = getLogger().child('CcApiContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const typeName = props['typeName'] as string;\n const exactIdentifier = props['exactIdentifier'] as string | undefined;\n const propertiesToReturn = (props['propertiesToReturn'] as string[]) || [];\n const propertyMatch = props['propertyMatch'] as Record<string, unknown> | undefined;\n const expectedMatchCount = (props['expectedMatchCount'] as string) || 'exactly-one';\n const dummyValue = props['dummyValue'];\n const ignoreErrorOnMissingContext = props['ignoreErrorOnMissingContext'] as boolean | undefined;\n\n if (!typeName) {\n throw new Error('CC API context provider requires typeName property');\n }\n\n this.logger.debug(\n `CC API lookup: ${typeName}${exactIdentifier ? ` (id: ${exactIdentifier})` : ''} (region: ${region})`\n );\n\n const client = new CloudControlClient({\n ...(region && { region }),\n });\n\n try {\n let resources: ResourceModel[];\n\n if (exactIdentifier) {\n // Get specific resource by identifier\n const resource = await this.getResource(client, typeName, exactIdentifier);\n resources = resource ? [resource] : [];\n } else {\n // List resources and filter\n resources = await this.listResources(client, typeName);\n\n // Apply property match filter\n if (propertyMatch && Object.keys(propertyMatch).length > 0) {\n resources = resources.filter((r) => this.matchesProperties(r, propertyMatch));\n }\n }\n\n // Validate match count\n this.validateMatchCount(resources, expectedMatchCount, typeName, exactIdentifier);\n\n if (resources.length === 0) {\n if (ignoreErrorOnMissingContext && dummyValue !== undefined) {\n this.logger.debug(`No resources found, returning dummy value`);\n return dummyValue;\n }\n throw new Error(\n `No ${typeName} resource found${exactIdentifier ? ` with identifier ${exactIdentifier}` : ''}`\n );\n }\n\n // Extract requested properties\n if (resources.length === 1) {\n return this.extractProperties(resources[0]!, propertiesToReturn);\n }\n\n return resources.map((r) => this.extractProperties(r, propertiesToReturn));\n } finally {\n client.destroy();\n }\n }\n\n /**\n * Get a single resource by identifier\n */\n private async getResource(\n client: CloudControlClient,\n typeName: string,\n identifier: string\n ): Promise<ResourceModel | null> {\n try {\n const response = await client.send(\n new GetResourceCommand({\n TypeName: typeName,\n Identifier: identifier,\n })\n );\n\n if (!response.ResourceDescription?.Properties) {\n return null;\n }\n\n return JSON.parse(response.ResourceDescription.Properties) as ResourceModel;\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * List all resources of a type\n */\n private async listResources(\n client: CloudControlClient,\n typeName: string\n ): Promise<ResourceModel[]> {\n const resources: ResourceModel[] = [];\n let nextToken: string | undefined;\n\n do {\n const response = await client.send(\n new ListResourcesCommand({\n TypeName: typeName,\n ...(nextToken && { NextToken: nextToken }),\n })\n );\n\n for (const desc of response.ResourceDescriptions ?? []) {\n if (desc.Properties) {\n resources.push(JSON.parse(desc.Properties) as ResourceModel);\n }\n }\n\n nextToken = response.NextToken;\n } while (nextToken);\n\n return resources;\n }\n\n /**\n * Check if resource matches property filter\n */\n private matchesProperties(\n resource: ResourceModel,\n propertyMatch: Record<string, unknown>\n ): boolean {\n for (const [key, expectedValue] of Object.entries(propertyMatch)) {\n const actualValue = this.getNestedProperty(resource, key);\n if (JSON.stringify(actualValue) !== JSON.stringify(expectedValue)) {\n return false;\n }\n }\n return true;\n }\n\n /**\n * Get nested property value using dot notation\n */\n private getNestedProperty(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split('.');\n let current: unknown = obj;\n for (const part of parts) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined;\n }\n current = (current as Record<string, unknown>)[part];\n }\n return current;\n }\n\n /**\n * Validate that the number of matches meets expectations\n */\n private validateMatchCount(\n resources: ResourceModel[],\n expectedMatchCount: string,\n typeName: string,\n identifier?: string\n ): void {\n const count = resources.length;\n const context = identifier ? ` with identifier ${identifier}` : '';\n\n switch (expectedMatchCount) {\n case 'exactly-one':\n if (count !== 1) {\n throw new Error(`Expected exactly one ${typeName}${context}, found ${count}`);\n }\n break;\n case 'at-least-one':\n if (count < 1) {\n throw new Error(`Expected at least one ${typeName}${context}, found none`);\n }\n break;\n case 'at-most-one':\n if (count > 1) {\n throw new Error(`Expected at most one ${typeName}${context}, found ${count}`);\n }\n break;\n case 'any':\n // No validation needed\n break;\n }\n }\n\n /**\n * Extract requested properties from resource model\n */\n private extractProperties(\n resource: ResourceModel,\n propertiesToReturn: string[]\n ): Record<string, unknown> {\n if (propertiesToReturn.length === 0) {\n return resource;\n }\n\n const result: Record<string, unknown> = {};\n for (const prop of propertiesToReturn) {\n result[prop] = this.getNestedProperty(resource, prop);\n }\n return result;\n }\n}\n\ntype ResourceModel = Record<string, unknown>;\n","import { EC2Client, DescribeImagesCommand } from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * AMI context provider\n *\n * Searches for the most recent AMI matching filters.\n * CDK provider type: \"ami\"\n */\nexport class AmiContextProvider implements ContextProvider {\n private logger = getLogger().child('AmiContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<string> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const owners = props['owners'] as string[] | undefined;\n const filters = props['filters'] as Record<string, string[]> | undefined;\n\n this.logger.debug(`Looking up AMI (region: ${region})`);\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n const ec2Filters = filters\n ? Object.entries(filters).map(([name, values]) => ({ Name: name, Values: values }))\n : undefined;\n\n const response = await client.send(\n new DescribeImagesCommand({\n ...(owners && { Owners: owners }),\n ...(ec2Filters && { Filters: ec2Filters }),\n })\n );\n\n const images = (response.Images ?? [])\n .filter((img) => img.ImageId && img.CreationDate)\n .sort((a, b) => (b.CreationDate ?? '').localeCompare(a.CreationDate ?? ''));\n\n if (images.length === 0) {\n throw new Error('No AMI found matching the specified filters');\n }\n\n const imageId = images[0]!.ImageId!;\n this.logger.debug(`Resolved AMI: ${imageId}`);\n return imageId;\n } finally {\n client.destroy();\n }\n }\n}\n","import { EC2Client, DescribeSecurityGroupsCommand } from '@aws-sdk/client-ec2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Security Group context provider\n *\n * Looks up security group details by ID.\n * CDK provider type: \"security-group\"\n */\nexport class SecurityGroupContextProvider implements ContextProvider {\n private logger = getLogger().child('SecurityGroupContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const securityGroupId = props['securityGroupId'] as string | undefined;\n const securityGroupName = props['securityGroupName'] as string | undefined;\n const vpcId = props['vpcId'] as string | undefined;\n\n this.logger.debug(\n `Looking up security group (id: ${securityGroupId}, name: ${securityGroupName}, region: ${region})`\n );\n\n const client = new EC2Client({\n ...(region && { region }),\n });\n\n try {\n const filters = [];\n if (securityGroupId) {\n filters.push({ Name: 'group-id', Values: [securityGroupId] });\n }\n if (securityGroupName) {\n filters.push({ Name: 'group-name', Values: [securityGroupName] });\n }\n if (vpcId) {\n filters.push({ Name: 'vpc-id', Values: [vpcId] });\n }\n\n const response = await client.send(\n new DescribeSecurityGroupsCommand({\n ...(filters.length > 0 && { Filters: filters }),\n ...(securityGroupId && !securityGroupName && { GroupIds: [securityGroupId] }),\n })\n );\n\n const groups = response.SecurityGroups ?? [];\n if (groups.length === 0) {\n throw new Error(\n `No security group found (id: ${securityGroupId}, name: ${securityGroupName})`\n );\n }\n\n const sg = groups[0]!;\n this.logger.debug(`Resolved security group: ${sg.GroupId}`);\n\n return {\n securityGroupId: sg.GroupId,\n allowAllOutbound: (sg.IpPermissionsEgress ?? []).some(\n (perm) =>\n perm.IpProtocol === '-1' && (perm.IpRanges ?? []).some((r) => r.CidrIp === '0.0.0.0/0')\n ),\n };\n } finally {\n client.destroy();\n }\n }\n}\n","import {\n ElasticLoadBalancingV2Client,\n DescribeLoadBalancersCommand,\n DescribeListenersCommand,\n} from '@aws-sdk/client-elastic-load-balancing-v2';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * Load Balancer context provider\n *\n * Looks up ALB/NLB details.\n * CDK provider type: \"load-balancer\"\n */\nexport class LoadBalancerContextProvider implements ContextProvider {\n private logger = getLogger().child('LoadBalancerContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const loadBalancerArn = props['loadBalancerArn'] as string | undefined;\n const loadBalancerType = props['loadBalancerType'] as string | undefined;\n\n this.logger.debug(`Looking up load balancer (arn: ${loadBalancerArn}, region: ${region})`);\n\n const client = new ElasticLoadBalancingV2Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(\n new DescribeLoadBalancersCommand({\n ...(loadBalancerArn && { LoadBalancerArns: [loadBalancerArn] }),\n })\n );\n\n let lbs = response.LoadBalancers ?? [];\n\n if (loadBalancerType) {\n lbs = lbs.filter((lb) => lb.Type === loadBalancerType);\n }\n\n if (lbs.length === 0) {\n throw new Error(`No load balancer found (arn: ${loadBalancerArn})`);\n }\n\n const lb = lbs[0]!;\n this.logger.debug(`Resolved load balancer: ${lb.LoadBalancerArn}`);\n\n return {\n loadBalancerArn: lb.LoadBalancerArn,\n loadBalancerCanonicalHostedZoneId: lb.CanonicalHostedZoneId,\n loadBalancerDnsName: lb.DNSName,\n vpcId: lb.VpcId,\n securityGroupIds: lb.SecurityGroups ?? [],\n ipAddressType: lb.IpAddressType,\n };\n } finally {\n client.destroy();\n }\n }\n}\n\n/**\n * Load Balancer Listener context provider\n *\n * Looks up ALB/NLB listener details.\n * CDK provider type: \"load-balancer-listener\"\n */\nexport class LoadBalancerListenerContextProvider implements ContextProvider {\n private logger = getLogger().child('LoadBalancerListenerContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const listenerArn = props['listenerArn'] as string | undefined;\n const loadBalancerArn = props['loadBalancerArn'] as string | undefined;\n const listenerPort = props['listenerPort'] as number | undefined;\n const listenerProtocol = props['listenerProtocol'] as string | undefined;\n\n this.logger.debug(\n `Looking up load balancer listener (arn: ${listenerArn}, lb: ${loadBalancerArn}, region: ${region})`\n );\n\n const client = new ElasticLoadBalancingV2Client({\n ...(region && { region }),\n });\n\n try {\n const response = await client.send(\n new DescribeListenersCommand({\n ...(listenerArn && { ListenerArns: [listenerArn] }),\n ...(loadBalancerArn && { LoadBalancerArn: loadBalancerArn }),\n })\n );\n\n let listeners = response.Listeners ?? [];\n\n if (listenerPort) {\n listeners = listeners.filter((l) => l.Port === listenerPort);\n }\n if (listenerProtocol) {\n listeners = listeners.filter((l) => l.Protocol === listenerProtocol);\n }\n\n if (listeners.length === 0) {\n throw new Error(\n `No listener found (arn: ${listenerArn}, lb: ${loadBalancerArn}, port: ${listenerPort})`\n );\n }\n\n const listener = listeners[0]!;\n this.logger.debug(`Resolved listener: ${listener.ListenerArn}`);\n\n return {\n listenerArn: listener.ListenerArn,\n listenerPort: listener.Port,\n securityGroupIds: [] as string[],\n };\n } finally {\n client.destroy();\n }\n }\n}\n","import { KMSClient, ListAliasesCommand } from '@aws-sdk/client-kms';\nimport type { ContextProvider, ContextProviderAwsConfig } from './index.js';\nimport { getLogger } from '../../utils/logger.js';\n\n/**\n * KMS Key context provider\n *\n * Looks up KMS key by alias name.\n * CDK provider type: \"key-provider\"\n */\nexport class KeyContextProvider implements ContextProvider {\n private logger = getLogger().child('KeyContextProvider');\n private awsConfig: ContextProviderAwsConfig | undefined;\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n this.awsConfig = awsConfig;\n }\n\n async resolve(props: Record<string, unknown>): Promise<unknown> {\n const region = (props['region'] as string) || this.awsConfig?.region;\n const aliasName = props['aliasName'] as string;\n\n if (!aliasName) {\n throw new Error('Key context provider requires aliasName property');\n }\n\n this.logger.debug(`Looking up KMS key by alias: ${aliasName} (region: ${region})`);\n\n const client = new KMSClient({\n ...(region && { region }),\n });\n\n try {\n // Normalize alias name\n const normalizedAlias = aliasName.startsWith('alias/') ? aliasName : `alias/${aliasName}`;\n\n let nextMarker: string | undefined;\n do {\n const response = await client.send(\n new ListAliasesCommand({\n ...(nextMarker && { Marker: nextMarker }),\n })\n );\n\n const match = (response.Aliases ?? []).find((a) => a.AliasName === normalizedAlias);\n if (match) {\n if (!match.TargetKeyId) {\n throw new Error(`KMS alias '${aliasName}' found but has no target key`);\n }\n this.logger.debug(`Resolved KMS key: ${match.TargetKeyId} (alias: ${aliasName})`);\n return { keyId: match.TargetKeyId };\n }\n\n nextMarker = response.NextMarker;\n } while (nextMarker);\n\n throw new Error(`No KMS key found with alias: ${aliasName}`);\n } finally {\n client.destroy();\n }\n }\n}\n","import type { MissingContext } from '../../types/assembly.js';\nimport { getLogger } from '../../utils/logger.js';\nimport { AZContextProvider } from './az-provider.js';\nimport { SSMContextProvider } from './ssm-provider.js';\nimport { HostedZoneContextProvider } from './hosted-zone-provider.js';\nimport { VpcContextProvider } from './vpc-provider.js';\nimport { CcApiContextProvider } from './cc-api-provider.js';\nimport { AmiContextProvider } from './ami-provider.js';\nimport { SecurityGroupContextProvider } from './security-group-provider.js';\nimport {\n LoadBalancerContextProvider,\n LoadBalancerListenerContextProvider,\n} from './load-balancer-provider.js';\nimport { KeyContextProvider } from './key-provider.js';\n\nconst PROVIDER_ERROR_KEY = '$providerError';\nconst TRANSIENT_CONTEXT_KEY = '$dontSaveContext';\n\n/**\n * Context provider interface\n */\nexport interface ContextProvider {\n /**\n * Resolve context value from AWS SDK\n * @param props Provider-specific query properties\n * @returns Resolved context value\n */\n resolve(props: Record<string, unknown>): Promise<unknown>;\n}\n\n/**\n * AWS client configuration for context providers\n */\nexport interface ContextProviderAwsConfig {\n region?: string;\n profile?: string;\n}\n\n/**\n * Context provider registry\n *\n * Maps provider type names to implementations.\n * Resolves missing context values by calling AWS SDK APIs.\n */\nexport class ContextProviderRegistry {\n private logger = getLogger().child('ContextProviderRegistry');\n private providers = new Map<string, ContextProvider>();\n\n constructor(awsConfig?: ContextProviderAwsConfig) {\n // Register built-in providers\n this.register('availability-zones', new AZContextProvider(awsConfig));\n this.register('ssm', new SSMContextProvider(awsConfig));\n this.register('hosted-zone', new HostedZoneContextProvider(awsConfig));\n this.register('vpc-provider', new VpcContextProvider(awsConfig));\n this.register('cc-api-provider', new CcApiContextProvider(awsConfig));\n this.register('ami', new AmiContextProvider(awsConfig));\n this.register('security-group', new SecurityGroupContextProvider(awsConfig));\n this.register('load-balancer', new LoadBalancerContextProvider(awsConfig));\n this.register('load-balancer-listener', new LoadBalancerListenerContextProvider(awsConfig));\n this.register('key-provider', new KeyContextProvider(awsConfig));\n }\n\n /**\n * Register a context provider\n */\n register(name: string, provider: ContextProvider): void {\n this.providers.set(name, provider);\n }\n\n /**\n * Resolve all missing context values\n *\n * @param missing Array of missing context entries from manifest\n * @returns Map of context key → resolved value\n */\n async resolve(missing: MissingContext[]): Promise<Record<string, unknown>> {\n const results: Record<string, unknown> = {};\n\n for (const entry of missing) {\n const provider = this.providers.get(entry.provider);\n\n if (!provider) {\n this.logger.warn(`No context provider registered for: ${entry.provider}`);\n results[entry.key] = {\n [PROVIDER_ERROR_KEY]: `Unknown context provider: ${entry.provider}`,\n [TRANSIENT_CONTEXT_KEY]: true,\n };\n continue;\n }\n\n try {\n this.logger.debug(`Resolving context: ${entry.provider} (key: ${entry.key})`);\n const value = await provider.resolve(entry.props);\n results[entry.key] = value;\n this.logger.debug(`Resolved context: ${entry.key}`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`Context provider '${entry.provider}' failed: ${message}`);\n results[entry.key] = {\n [PROVIDER_ERROR_KEY]: message,\n [TRANSIENT_CONTEXT_KEY]: true,\n };\n }\n }\n\n return results;\n }\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { resolve, join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * CDK configuration loaded from cdk.json and environment variables\n */\nexport interface CdkConfig {\n app?: string;\n output?: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * cdkd-specific configuration extracted from cdk.json context or environment\n */\nexport interface CdkdConfig {\n stateBucket?: string;\n}\n\n/**\n * Load a JSON config file and return as CdkConfig, or null if not found.\n */\nfunction loadJsonConfig(filePath: string): CdkConfig | null {\n const logger = getLogger();\n\n if (!existsSync(filePath)) {\n return null;\n }\n\n try {\n const content = readFileSync(filePath, 'utf-8');\n const config = JSON.parse(content) as CdkConfig;\n logger.debug(`Loaded config from ${filePath}`);\n return config;\n } catch (error) {\n logger.warn(\n `Failed to parse ${filePath}: ${error instanceof Error ? error.message : String(error)}`\n );\n return null;\n }\n}\n\n/**\n * Load cdk.json from the current working directory\n */\nexport function loadCdkJson(cwd?: string): CdkConfig | null {\n const dir = cwd || process.cwd();\n return loadJsonConfig(resolve(dir, 'cdk.json'));\n}\n\n/**\n * Load user-level defaults from ~/.cdk.json\n *\n * CDK CLI reads this as user-level defaults (lowest priority).\n * Context values from ~/.cdk.json are merged below project cdk.json context.\n */\nexport function loadUserCdkJson(): CdkConfig | null {\n return loadJsonConfig(join(homedir(), '.cdk.json'));\n}\n\n/**\n * Resolve the --app option from CLI, cdk.json, or environment\n *\n * Priority: CLI option > CDKD_APP env > cdk.json app field\n */\nexport function resolveApp(cliApp?: string): string | undefined {\n if (cliApp) return cliApp;\n\n const envApp = process.env['CDKD_APP'];\n if (envApp) return envApp;\n\n const cdkJson = loadCdkJson();\n return cdkJson?.app ?? undefined;\n}\n\n/**\n * Source of a resolved state-bucket name.\n *\n * Reported by `cdkd state info` so users can see *why* a particular bucket was\n * chosen. The CLI flag wins over the env var, which wins over cdk.json, which\n * falls through to a default name derived from the STS account id.\n */\nexport type StateBucketSource = 'cli-flag' | 'env' | 'cdk.json' | 'default' | 'default-legacy';\n\n/**\n * Result of resolving the state bucket, including the source that won.\n */\nexport interface ResolvedStateBucket {\n bucket: string;\n source: StateBucketSource;\n}\n\n/**\n * Resolve the `--capture-observed-state` / `--no-capture-observed-state`\n * option's effective value, falling through to `cdk.json\n * context.cdkd.captureObservedState` when the CLI flag was not passed.\n *\n * Commander reports `--no-X` flags by emitting `x: false` (which the deploy\n * command's TS type carries as `captureObservedState: boolean`). We can't\n * tell from that whether the user explicitly opted out vs. accepted the\n * default `true`, so the cdk.json fallback only fires when the CLI value\n * is the implicit default (`true`). Pass `--no-capture-observed-state`\n * to overrule a `cdk.json: { captureObservedState: true }` explicitly.\n */\nexport function resolveCaptureObservedState(cliValue: boolean): boolean {\n if (cliValue === false) return false;\n const cdkJson = loadCdkJson();\n const cdkdContext = cdkJson?.context?.['cdkd'] as Record<string, unknown> | undefined;\n const v = cdkdContext?.['captureObservedState'];\n if (typeof v === 'boolean') return v;\n return true;\n}\n\n/**\n * Resolve the effective value for \"should cdkd skip the stack-name\n * prefix on user-supplied physical names?\" on `cdkd deploy`.\n *\n * Returns `true` when cdkd should SKIP prepending the stack name to\n * user-declared physical names (e.g. an `iam.Role` whose `roleName:\n * 'my-role'` was set explicitly by the user). Returns `false` when\n * cdkd should KEEP the legacy behavior of prepending the stack name\n * (the pre-v0.94.0 default; now an explicit opt-in).\n *\n * **Default flipped in v0.94.0** ([#299](https://github.com/go-to-k/cdkd/issues/299)).\n * Prior to v0.94.0 the default was `false` (= legacy prefixing) and\n * `--no-prefix-user-supplied-names` was the opt-in. Now the default\n * is `true` (= unprefixed) and `--prefix-user-supplied-names` is the\n * opt-in to restore legacy prefixing. Deploying a CDK app with\n * `roleName: 'my-role'` produces an AWS resource named `my-role` by\n * default; consistent across every resource type out of the box.\n *\n * Auto-generated names (where the user did NOT supply a physical\n * name) are unaffected — every provider's `generateResourceName`\n * call sets `userSupplied: false` on the logical-id fallback path,\n * so the prefix stays for those resources regardless of this flag.\n *\n * Resolution chain (highest wins):\n *\n * 1. `--prefix-user-supplied-names` CLI flag → Commander emits\n * `prefixUserSuppliedNames: true` when the flag is passed.\n * That explicit opt-in to legacy prefixing short-circuits the\n * lookup and returns `false` regardless of env / cdk.json.\n * 2. `CDKD_PREFIX_USER_SUPPLIED_NAMES=true` env var → also returns\n * `false` (= keep legacy prefixing).\n * 3. `cdk.json` `context.cdkd.prefixUserSuppliedNames: true` →\n * same effect.\n * 4. Deprecated `--no-prefix-user-supplied-names` CLI flag (Commander\n * emits `noPrefixUserSuppliedNames: false`) → no-op vs the new\n * default; emits a deprecation warning. Pre-v0.94.0 this was\n * the way to opt in to skipping the prefix; now it matches the\n * default and is kept only for backward-compat / scripts that\n * already set it.\n * 5. Deprecated `CDKD_NO_PREFIX_USER_SUPPLIED_NAMES=true` env var\n * and `cdk.json context.cdkd.noPrefixUserSuppliedNames: true` →\n * same deprecation-warning + no-op semantics.\n * 6. Default `true` (skip prefix — new default in v0.94.0).\n *\n * Mirrors {@link resolveCaptureObservedState}'s pattern. The cliValue\n * argument carries the Commander-emitted boolean for\n * `--prefix-user-supplied-names`. The deprecated\n * `--no-prefix-user-supplied-names` flag is detected via the pre-parse\n * argv walk in {@link warnDeprecatedNoPrefixCliFlag} — NOT here, because\n * declaring both flag forms as separate Commander Options collapses\n * them onto a single key (`noPrefixUserSuppliedNames` would be\n * permanently `undefined` at runtime). Commander's automatic `--no-X`\n * negation still parses the deprecated form without error; it just\n * negates `prefixUserSuppliedNames` to its default `false` (= skip\n * prefix), which matches the new v0.94.0 default semantically.\n */\nexport interface ResolveSkipPrefixOptions {\n /**\n * Commander-emitted value of `--prefix-user-supplied-names` (the new\n * opt-in to legacy prefixing). `true` when the user passed the flag;\n * `false` (= default) when they did not. When `true`, cdkd KEEPS\n * legacy prefixing and {@link resolveSkipPrefix} returns `false`.\n */\n prefixUserSuppliedNames?: boolean;\n}\n\n/**\n * Pre-parse argv walk that surfaces the deprecation warning when the\n * user explicitly passes the legacy `--no-prefix-user-supplied-names`\n * flag. Commander's auto-negation of `--prefix-user-supplied-names`\n * accepts the flag without surfacing it as a distinct option key, so\n * this walk is the only way to catch it for the warning. Call once at\n * the top of every deploy invocation, before {@link resolveSkipPrefix}.\n *\n * Matches the literal `--no-prefix-user-supplied-names` token (and its\n * `--no-prefix-user-supplied-names=<value>` form) so scripts that pass\n * the flag with an explicit value still see the warning.\n */\nexport function warnDeprecatedNoPrefixCliFlag(argv: readonly string[] = process.argv): void {\n const seen = argv.some(\n (a) =>\n a === '--no-prefix-user-supplied-names' || a.startsWith('--no-prefix-user-supplied-names=')\n );\n if (seen) {\n getLogger().warn(\n '--no-prefix-user-supplied-names is deprecated since v0.94.0 — ' +\n 'skipping the prefix is now the default. Remove the flag.'\n );\n }\n}\n\nexport function resolveSkipPrefix(opts: ResolveSkipPrefixOptions = {}): boolean {\n const logger = getLogger();\n\n // Tier 1: --prefix-user-supplied-names CLI flag → keep legacy\n // prefixing. Wins over every other source.\n if (opts.prefixUserSuppliedNames === true) {\n return false;\n }\n\n // Tier 2: CDKD_PREFIX_USER_SUPPLIED_NAMES=true env var → also keep\n // legacy prefixing.\n const envPrefix = process.env['CDKD_PREFIX_USER_SUPPLIED_NAMES'];\n if (envPrefix === 'true') {\n return false;\n }\n\n // Tier 3: cdk.json context.cdkd.prefixUserSuppliedNames: true →\n // same effect.\n const cdkJson = loadCdkJson();\n const cdkdContext = cdkJson?.context?.['cdkd'] as Record<string, unknown> | undefined;\n const v = cdkdContext?.['prefixUserSuppliedNames'];\n if (typeof v === 'boolean' && v === true) {\n return false;\n }\n\n // Deprecated CDKD_NO_PREFIX_USER_SUPPLIED_NAMES env var +\n // cdk.json context.cdkd.noPrefixUserSuppliedNames: emit a\n // deprecation warning when set; they now match the default and are\n // no-ops in effect. (The CLI-flag equivalent is detected via\n // warnDeprecatedNoPrefixCliFlag — see the docstring above.)\n const deprecatedEnv = process.env['CDKD_NO_PREFIX_USER_SUPPLIED_NAMES'];\n if (deprecatedEnv === 'true') {\n logger.warn(\n 'CDKD_NO_PREFIX_USER_SUPPLIED_NAMES is deprecated since v0.94.0 — ' +\n 'skipping the prefix is now the default. Unset the env var.'\n );\n }\n const deprecatedCdkJson = cdkdContext?.['noPrefixUserSuppliedNames'];\n if (typeof deprecatedCdkJson === 'boolean' && deprecatedCdkJson === true) {\n logger.warn(\n 'cdk.json context.cdkd.noPrefixUserSuppliedNames is deprecated since v0.94.0 — ' +\n 'skipping the prefix is now the default. Remove the entry.'\n );\n }\n\n // Tier 6: default → skip prefix (the v0.94.0 flip).\n return true;\n}\n\n/**\n * Resolve the --state-bucket option from CLI, cdk.json context, or environment\n *\n * Priority: CLI option > CDKD_STATE_BUCKET env > cdk.json context.cdkd.stateBucket\n */\nexport function resolveStateBucket(cliBucket?: string): string | undefined {\n return resolveStateBucketWithSource(cliBucket)?.bucket;\n}\n\n/**\n * Like {@link resolveStateBucket}, but also reports which source provided the\n * value. Returns `undefined` when no synchronous source is configured (caller\n * should fall back to the STS-derived default).\n */\nexport function resolveStateBucketWithSource(cliBucket?: string): ResolvedStateBucket | undefined {\n if (cliBucket) return { bucket: cliBucket, source: 'cli-flag' };\n\n const envBucket = process.env['CDKD_STATE_BUCKET'];\n if (envBucket) return { bucket: envBucket, source: 'env' };\n\n const cdkJson = loadCdkJson();\n const cdkdContext = cdkJson?.context?.['cdkd'] as Record<string, unknown> | undefined;\n const bucket = cdkdContext?.['stateBucket'];\n if (typeof bucket === 'string') return { bucket, source: 'cdk.json' };\n\n return undefined;\n}\n\n/**\n * Generate default state bucket name from account info.\n *\n * Format: `cdkd-state-{accountId}` (region intentionally omitted).\n *\n * S3 bucket names are globally unique, so embedding the profile region in the\n * default name made teammates with different profile regions look up\n * different buckets and silently fork their state. Dropping the region from\n * the default lets the whole team converge on a single bucket — its actual\n * region is auto-detected at runtime via `GetBucketLocation`\n * ({@link import('../utils/aws-region-resolver.js').resolveBucketRegion}).\n */\nexport function getDefaultStateBucketName(accountId: string): string {\n return `cdkd-state-${accountId}`;\n}\n\n/**\n * Generate the **legacy** default state bucket name.\n *\n * Format: `cdkd-state-{accountId}-{region}` — the pre-v0.8 default.\n *\n * Used only by the backwards-compatibility fallback in\n * {@link resolveStateBucketWithDefault}: if the new region-free bucket is not\n * found, cdkd checks the legacy region-suffixed name so users who already\n * bootstrapped under the old default keep working until they migrate.\n *\n * TODO(remove-bc-after-1.x): Remove this helper and all callers when the\n * backwards-compat read path is dropped (tracked in PR 99 of the\n * region/state refactor — see `docs/plans/04-state-bucket-naming.md`).\n */\nexport function getLegacyStateBucketName(accountId: string, region: string): string {\n return `cdkd-state-${accountId}-${region}`;\n}\n\n/**\n * Resolve state bucket with STS fallback.\n *\n * Priority:\n * 1. Explicit value from `--state-bucket` / `CDKD_STATE_BUCKET` /\n * `cdk.json context.cdkd.stateBucket` — used as-is.\n * 2. Default name `cdkd-state-{accountId}` (new). Verified to exist via\n * `HeadBucket` against a region-agnostic S3 client (the actual region is\n * resolved separately by {@link\n * import('../utils/aws-region-resolver.js').resolveBucketRegion}).\n * 3. Legacy name `cdkd-state-{accountId}-{region}` — only consulted if step 2\n * returned `NoSuchBucket` / 404. Logs a deprecation warning.\n * 4. Neither found → throw a \"run cdkd bootstrap\" error pointing at the new\n * name.\n *\n * `region` is the CLI's *profile* region; it is used only to construct the\n * legacy fallback name. The actual state-bucket region is resolved later by\n * `resolveBucketRegion`, so the caller does not need to pass the bucket's\n * real region here.\n *\n * Requires AWS credentials to be configured (STS GetCallerIdentity).\n *\n * The bucket name is logged at debug level only — it includes the AWS account\n * id, which would leak via screenshots / public CI logs if printed by default.\n * Use `cdkd state info` to inspect on demand, or pass `--verbose` to surface\n * it in routine commands.\n */\nexport async function resolveStateBucketWithDefault(\n cliBucket: string | undefined,\n region: string\n): Promise<string> {\n return (await resolveStateBucketWithDefaultAndSource(cliBucket, region)).bucket;\n}\n\n/**\n * Like {@link resolveStateBucketWithDefault}, but also reports which source\n * provided the value (`'cli-flag'` / `'env'` / `'cdk.json'` / `'default'` /\n * `'default-legacy'`).\n */\nexport async function resolveStateBucketWithDefaultAndSource(\n cliBucket: string | undefined,\n region: string\n): Promise<ResolvedStateBucket> {\n // Step 1: explicit value short-circuits the lookup chain.\n const syncResult = resolveStateBucketWithSource(cliBucket);\n if (syncResult) return syncResult;\n\n const logger = getLogger();\n logger.debug('No state bucket specified, resolving default from account...');\n\n const { GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');\n const { S3Client } = await import('@aws-sdk/client-s3');\n const { getAwsClients } = await import('../utils/aws-clients.js');\n const awsClients = getAwsClients();\n const identity = await awsClients.sts.send(new GetCallerIdentityCommand({}));\n const accountId = identity.Account!;\n\n const newName = getDefaultStateBucketName(accountId);\n // TODO(remove-bc-after-1.x): legacy name kept for the backwards-compat read\n // path; remove together with the fallback branch below in PR 99.\n const legacyName = getLegacyStateBucketName(accountId, region);\n\n // Use a region-agnostic client (us-east-1) for the existence checks. S3\n // returns 301 / 404 globally for both names — we don't need the real bucket\n // region to ask whether the bucket exists. The state-bucket S3 client used\n // for actual reads/writes is rebuilt against the bucket's real region via\n // `resolveBucketRegion` later in the flow.\n const probe = new S3Client({ region: 'us-east-1' });\n try {\n const newExists = await bucketExists(probe, newName);\n const legacyExists = await bucketExists(probe, legacyName);\n\n // Step 2 / 3: pick the bucket that actually has state.\n //\n // Three sub-cases when one or both default buckets exist:\n //\n // a. Only new exists → use new (no legacy to consider).\n // b. Only legacy exists → use legacy + deprecation warning, point\n // the user at `cdkd state migrate`.\n // c. Both exist → previously we always picked new. That hid the\n // common upgrade path: legacy bucket from an earlier cdkd\n // version + an empty new bucket left behind by a partial\n // migration / probe / bootstrap. Picking new in that case\n // makes the next deploy think the stack is brand-new and\n // collide with the existing AWS resources. Now we look at\n // whether new actually has state under `cdkd/`. If new is\n // empty AND legacy has state, fall back to legacy with a\n // strong warning telling the user to run migrate.\n if (newExists && legacyExists) {\n const newHasState = await bucketHasAnyState(probe, newName);\n if (!newHasState) {\n const legacyHasState = await bucketHasAnyState(probe, legacyName);\n if (legacyHasState) {\n logger.warn(\n `Both '${newName}' (new default) and '${legacyName}' (legacy default) exist, ` +\n `but the new bucket is empty and the legacy one has state. Reading from legacy. ` +\n `Run \\`cdkd state migrate --region ${region}\\` to copy the state into the new ` +\n `bucket and stop seeing this warning.`\n );\n return { bucket: legacyName, source: 'default-legacy' };\n }\n }\n logger.debug(`State bucket: ${newName}`);\n return { bucket: newName, source: 'default' };\n }\n\n if (newExists) {\n // Logged at debug only — see resolveStateBucketWithDefault doc-comment.\n logger.debug(`State bucket: ${newName}`);\n return { bucket: newName, source: 'default' };\n }\n\n // TODO(remove-bc-after-1.x): drop the legacy fallback branch in PR 99.\n if (legacyExists) {\n logger.warn(\n `Using legacy state bucket name '${legacyName}'. ` +\n `The default has changed to '${newName}'. To migrate, run:\\n\\n` +\n ` cdkd state migrate --region ${region}\\n\\n` +\n `(add --remove-legacy to delete the legacy bucket after a successful copy; ` +\n `legacy support will be dropped in a future release.)`\n );\n return { bucket: legacyName, source: 'default-legacy' };\n }\n\n // Step 4: neither bucket exists.\n throw new Error(\n `No cdkd state bucket found for account ${accountId}. ` +\n `Looked for '${newName}' (current default) and '${legacyName}' (legacy default). ` +\n `Run 'cdkd bootstrap' to create '${newName}'.`\n );\n } finally {\n probe.destroy();\n }\n}\n\n/**\n * Return `true` if the bucket has at least one object under the cdkd state\n * prefix (`cdkd/`). Used to disambiguate \"this bucket holds state\" from\n * \"this bucket exists but is empty\" — the latter happens when a previous\n * `cdkd state migrate` probe / bootstrap left a fresh bucket behind that\n * was never written to.\n *\n * Errors (network, access denied) are treated as \"don't know\" and return\n * `true` — biases toward NOT silently picking the legacy bucket when the\n * new one's state is uncertain. False positives here are harmless (the\n * downstream getState call will surface the real read error); a false\n * negative would silently route to legacy and be confusing.\n */\nasync function bucketHasAnyState(\n client: import('@aws-sdk/client-s3').S3Client,\n bucketName: string\n): Promise<boolean> {\n const { ListObjectsV2Command } = await import('@aws-sdk/client-s3');\n try {\n const resp = await client.send(\n new ListObjectsV2Command({\n Bucket: bucketName,\n Prefix: 'cdkd/',\n MaxKeys: 1,\n })\n );\n return (resp.KeyCount ?? 0) > 0;\n } catch {\n // Conservative: if we can't tell, assume the bucket has state so we\n // don't silently fall through to the legacy bucket.\n return true;\n }\n}\n\n/**\n * Probe whether an S3 bucket exists from this account's perspective.\n *\n * Returns:\n * - `true` for any 2xx (`HeadBucket` succeeded) **or** 301 (the bucket\n * exists, just in a different region — we can still use it because the\n * real region is resolved later by `resolveBucketRegion`).\n * - `true` for 403 (we lack permission to head it, but it exists; let the\n * state-backend produce a more specific error later).\n * - `false` for 404 / `NotFound` / `NoSuchBucket`.\n * - Re-throws anything else so credential / network failures aren't silently\n * swallowed by the lookup chain.\n */\nasync function bucketExists(\n client: import('@aws-sdk/client-s3').S3Client,\n bucketName: string\n): Promise<boolean> {\n const { HeadBucketCommand } = await import('@aws-sdk/client-s3');\n try {\n await client.send(new HeadBucketCommand({ Bucket: bucketName }));\n return true;\n } catch (error) {\n const err = error as {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n message?: string;\n };\n const status = err.$metadata?.httpStatusCode;\n if (err.name === 'NotFound' || err.name === 'NoSuchBucket' || status === 404) {\n return false;\n }\n // 301 = bucket exists in a different region (cross-region HEAD redirect).\n // 403 = bucket exists but we lack `s3:ListBucket` — treat as existing so\n // the downstream operation surfaces the real \"access denied\" error.\n if (status === 301 || status === 403) {\n return true;\n }\n // AWS SDK v3 synthetic Unknown error — covers the empty-body 301 redirect\n // case where the SDK fails to parse the status. We can't distinguish from\n // here, so re-throw and let the caller decide.\n throw error;\n }\n}\n","import { existsSync, mkdirSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';\nimport { AppExecutor } from './app-executor.js';\nimport { AssemblyReader, type StackInfo } from './assembly-reader.js';\nimport { ContextStore } from './context-store.js';\nimport { ContextProviderRegistry } from './context-providers/index.js';\nimport type { AssemblyManifest } from '../types/assembly.js';\nimport { loadCdkJson, loadUserCdkJson } from '../cli/config-loader.js';\nimport { getLogger } from '../utils/logger.js';\nimport { SynthesisError } from '../utils/error-handler.js';\n\n/**\n * Synthesis options\n */\nexport interface SynthesisOptions {\n /** CDK app command (e.g., \"node app.ts\") */\n app: string;\n\n /** Output directory for synthesis (default: \"cdk.out\") */\n output?: string;\n\n /** AWS profile to use */\n profile?: string;\n\n /** AWS region */\n region?: string;\n\n /** Context key-value pairs (CLI -c/--context) */\n context?: Record<string, string>;\n}\n\n/**\n * Synthesis result\n */\nexport interface SynthesisResult {\n /** Cloud assembly manifest */\n manifest: AssemblyManifest;\n\n /** Assembly directory (absolute path) */\n assemblyDir: string;\n\n /** All stacks in the assembly */\n stacks: StackInfo[];\n}\n\n/**\n * CDK app synthesizer\n *\n * Replaces @aws-cdk/toolkit-lib with self-implemented:\n * - Subprocess execution of CDK app\n * - Cloud assembly manifest parsing\n * - Context provider loop (missing context → SDK lookup → re-synthesize)\n */\nexport class Synthesizer {\n private logger = getLogger().child('Synthesizer');\n private appExecutor = new AppExecutor();\n private assemblyReader = new AssemblyReader();\n private contextStore = new ContextStore();\n\n /**\n * Synthesize CDK app to cloud assembly\n *\n * Implements the context provider loop:\n * 1. Merge context (cdk.json context + cdk.context.json + CLI -c)\n * 2. Execute CDK app subprocess\n * 3. Read manifest.json\n * 4. If missing context → resolve via providers → save to cdk.context.json → re-execute\n * 5. Return assembly with stacks\n */\n async synthesize(options: SynthesisOptions): Promise<SynthesisResult> {\n // CDK CLI compatibility: if --app points at an existing directory, treat it\n // as a pre-synthesized cloud assembly and skip subprocess execution.\n // See aws-cdk/lib/cxapp/exec.ts: \"bypass 'synth' if app points to a cloud assembly\".\n const appPath = resolve(options.app);\n if (existsSync(appPath) && statSync(appPath).isDirectory()) {\n this.logger.debug(`Using pre-synthesized cloud assembly at ${appPath}`);\n const manifest = this.assemblyReader.readManifest(appPath);\n const stacks = this.assemblyReader.getAllStacks(appPath, manifest);\n this.logger.debug(`Loaded ${stacks.length} stack(s) from pre-synthesized assembly`);\n return { manifest, assemblyDir: appPath, stacks };\n }\n\n const outputDir = resolve(options.output || 'cdk.out');\n\n // Ensure output directory exists\n mkdirSync(outputDir, { recursive: true });\n\n // Load static context (doesn't change during loop)\n // Priority: defaults < ~/.cdk.json < cdk.json < cdk.context.json < CLI -c\n const userCdkJson = loadUserCdkJson();\n const userContext = (userCdkJson?.context as Record<string, unknown>) ?? {};\n const cdkJson = loadCdkJson();\n const cdkJsonContext = (cdkJson?.context as Record<string, unknown>) ?? {};\n const cliContext = (options.context as Record<string, unknown>) ?? {};\n\n // CDK CLI injects these context values by default for framework compatibility\n const cdkDefaults: Record<string, unknown> = {\n 'aws:cdk:enable-path-metadata': true,\n 'aws:cdk:enable-asset-metadata': true,\n 'aws:cdk:version-reporting': true,\n 'aws:cdk:bundling-stacks': ['**'],\n };\n\n // Resolve AWS account/region for context passing\n const region = options.region || process.env['AWS_REGION'] || process.env['AWS_DEFAULT_REGION'];\n let accountId: string | undefined;\n try {\n const stsClient = new STSClient({ ...(region && { region }) });\n const identity = await stsClient.send(new GetCallerIdentityCommand({}));\n accountId = identity.Account;\n stsClient.destroy();\n } catch {\n this.logger.debug('Could not resolve AWS account ID via STS');\n }\n\n // Context provider loop\n let previousMissingKeys: Set<string> | undefined;\n const contextProviderRegistry = new ContextProviderRegistry({\n ...(region && { region }),\n ...(options.profile && { profile: options.profile }),\n });\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n // Load cdk.context.json (re-read each iteration — providers may have updated it)\n const cdkContextJson = this.contextStore.load();\n\n // Merge context: defaults < ~/.cdk.json < cdk.json < cdk.context.json < CLI -c (CLI wins)\n const mergedContext: Record<string, unknown> = {\n ...cdkDefaults,\n ...userContext,\n ...cdkJsonContext,\n ...cdkContextJson,\n ...cliContext,\n };\n\n // Execute CDK app\n this.logger.debug('Executing CDK app...');\n await this.appExecutor.execute({\n app: options.app,\n outputDir,\n context: mergedContext,\n ...(region && { region }),\n ...(accountId && { accountId }),\n });\n\n // Read manifest\n const manifest = this.assemblyReader.readManifest(outputDir);\n\n // Check for missing context\n if (!manifest.missing || manifest.missing.length === 0) {\n // Synthesis complete\n const stacks = this.assemblyReader.getAllStacks(outputDir, manifest);\n this.logger.debug(`Synthesis complete: ${stacks.length} stack(s)`);\n\n return { manifest, assemblyDir: outputDir, stacks };\n }\n\n // Missing context detected\n const missingKeys = new Set(manifest.missing.map((m) => m.key));\n this.logger.debug(`Missing context: ${manifest.missing.length} value(s)`);\n\n // Check for no progress (same missing keys as last iteration)\n if (previousMissingKeys && setsEqual(missingKeys, previousMissingKeys)) {\n throw new SynthesisError(\n 'Context resolution made no progress. ' +\n `Missing context keys: ${[...missingKeys].join(', ')}. ` +\n 'Ensure cdk.context.json is correctly configured or required AWS permissions are granted.'\n );\n }\n previousMissingKeys = missingKeys;\n\n // Resolve missing context via providers\n this.logger.info('Resolving missing context...');\n const resolved = await contextProviderRegistry.resolve(manifest.missing);\n\n // Save resolved values to cdk.context.json\n this.contextStore.save(resolved);\n\n // Loop: re-execute CDK app with updated context\n this.logger.debug('Re-synthesizing with resolved context...');\n }\n }\n\n /**\n * List stack names in CDK app\n */\n async listStacks(options: SynthesisOptions): Promise<string[]> {\n const result = await this.synthesize(options);\n return result.stacks.map((s) => s.stackName);\n }\n}\n\n/**\n * Check if two sets contain the same elements\n */\nfunction setsEqual<T>(a: Set<T>, b: Set<T>): boolean {\n if (a.size !== b.size) return false;\n for (const item of a) {\n if (!b.has(item)) return false;\n }\n return true;\n}\n","import { createReadStream, statSync } from 'node:fs';\nimport { join, basename } from 'node:path';\nimport { S3Client, HeadObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';\nimport type { FileAsset } from '../types/assets.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Publishes file assets to S3\n *\n * Handles:\n * - Placeholder resolution (${AWS::AccountId}, ${AWS::Region})\n * - Existence check (skip if already uploaded)\n * - ZIP packaging for directory assets\n * - Direct file upload for single files\n */\nexport class FileAssetPublisher {\n private logger = getLogger().child('FileAssetPublisher');\n\n /**\n * Publish a file asset to S3\n *\n * @param assetHash Asset hash (ID)\n * @param asset File asset definition\n * @param cdkOutputDir CDK output directory (cdk.out)\n * @param accountId AWS account ID\n * @param region AWS region\n * @param profile AWS profile (optional)\n */\n async publish(\n assetHash: string,\n asset: FileAsset,\n cdkOutputDir: string,\n accountId: string,\n region: string,\n _profile?: string\n ): Promise<void> {\n // Process each destination\n for (const [, dest] of Object.entries(asset.destinations)) {\n const bucketName = this.resolvePlaceholders(dest.bucketName, accountId, region);\n const objectKey = this.resolvePlaceholders(dest.objectKey, accountId, region);\n const destRegion = dest.region\n ? this.resolvePlaceholders(dest.region, accountId, region)\n : region;\n\n this.logger.debug(\n `Publishing file asset ${asset.displayName || assetHash} → s3://${bucketName}/${objectKey}`\n );\n\n const client = new S3Client({\n region: destRegion,\n });\n\n try {\n // Check if already exists\n if (await this.objectExists(client, bucketName, objectKey)) {\n this.logger.debug(`Asset already exists, skipping: s3://${bucketName}/${objectKey}`);\n continue;\n }\n\n // Determine source path\n const sourcePath = join(cdkOutputDir, asset.source.path);\n\n if (asset.source.packaging === 'zip') {\n // ZIP packaging: create zip archive and upload\n await this.uploadZip(client, sourcePath, bucketName, objectKey);\n } else {\n // Direct file upload\n await this.uploadFile(client, sourcePath, bucketName, objectKey);\n }\n\n this.logger.debug(`✅ Published: s3://${bucketName}/${objectKey}`);\n } finally {\n client.destroy();\n }\n }\n }\n\n /**\n * Check if an S3 object exists\n */\n private async objectExists(client: S3Client, bucket: string, key: string): Promise<boolean> {\n try {\n await client.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));\n return true;\n } catch (error) {\n const err = error as {\n name?: string;\n message?: string;\n $metadata?: { httpStatusCode?: number };\n };\n if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) {\n return false;\n }\n // Provide helpful error for common issues\n const statusCode = err.$metadata?.httpStatusCode;\n if (statusCode === 301 || err.name === 'PermanentRedirect') {\n throw new Error(\n `S3 bucket '${bucket}' is in a different region. ` +\n `Use --region to specify the correct region, or check asset manifest destination.`\n );\n }\n throw new Error(\n `Failed to check S3 object s3://${bucket}/${key}: ${err.name || 'UnknownError'}: ${err.message || String(error)}`\n );\n }\n }\n\n /**\n * Upload a single file to S3\n */\n private async uploadFile(\n client: S3Client,\n filePath: string,\n bucket: string,\n key: string\n ): Promise<void> {\n const stat = statSync(filePath);\n const stream = createReadStream(filePath);\n\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n Body: stream,\n ContentLength: stat.size,\n })\n );\n }\n\n /**\n * Create ZIP archive and upload to S3\n */\n private async uploadZip(\n client: S3Client,\n dirPath: string,\n bucket: string,\n key: string\n ): Promise<void> {\n const archiver = await import('archiver');\n\n // Collect all archive data into a buffer before uploading\n const body = await new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n const archive = archiver.default('zip', { zlib: { level: 9 } });\n\n archive.on('data', (chunk: Buffer) => chunks.push(chunk));\n archive.on('end', () => resolve(Buffer.concat(chunks)));\n archive.on('error', reject);\n\n // Check if dirPath is a file or directory\n const stat = statSync(dirPath);\n if (stat.isDirectory()) {\n archive.directory(dirPath, false);\n } else {\n archive.file(dirPath, { name: basename(dirPath) });\n }\n\n void archive.finalize();\n });\n\n await client.send(\n new PutObjectCommand({\n Bucket: bucket,\n Key: key,\n Body: body,\n ContentLength: body.length,\n })\n );\n }\n\n /**\n * Replace placeholders in destination values\n */\n private resolvePlaceholders(\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","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { DockerImageAssetSource } from '../types/assets.js';\nimport { getLogger } from '../utils/logger.js';\n\nconst execFileAsync = promisify(execFile);\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\n * via `cdkd local invoke`).\n *\n * Invariants preserved across the two callers:\n * - `maxBuffer: 50 * 1024 * 1024` so a verbose `docker build` log doesn't\n * blow up `execFile`'s default 1 MB buffer.\n * - Build args iterate in the order `Object.entries(...)` returns them so\n * the resulting layer cache is stable across runs (any reordering would\n * bust caches for both the publisher and local invoke).\n * - Errors carry the captured stderr so the user can re-run `docker build`\n * directly to debug. The error class is parameterized: each consumer\n * wraps the failure with its own typed error (`AssetError` for the\n * publisher, `LocalInvokeBuildError` for local invoke) so the existing\n * error-handling chain on each side keeps working unchanged.\n *\n * `platform` is new in PR 5: container Lambdas declare `Architectures:\n * [x86_64]` (default) or `[arm64]`, and the local-invoke caller MUST pass the\n * matching `linux/amd64` / `linux/arm64` so the built image can run on the\n * developer's host (which may have the opposite arch). The publisher caller\n * defaults to `undefined` for backward compatibility — passing through is\n * the no-op, the user's local docker default arch picks up.\n */\n\n/**\n * Build a Docker image from a CDK asset's source description.\n *\n * @param asset The `DockerImageAsset` entry from the cdk asset\n * manifest (carries `directory`, `dockerFile`, build args,\n * target, outputs).\n * @param cdkOutDir Absolute path to the CDK output directory (`cdk.out`).\n * Used to resolve `asset.source.directory` to a real\n * build context on disk.\n * @param tag Local image tag to apply (`-t`). The caller chooses a\n * deterministic tag so subsequent runs hit Docker's\n * layer cache (publisher uses `cdkd-asset-<hash>`;\n * local-invoke uses `cdkd-local-invoke-<hash>`).\n * @param platform Optional `--platform` value (e.g. `linux/amd64`,\n * `linux/arm64`). When `undefined` the flag is omitted\n * and Docker uses its default platform.\n * @param wrapError Function the caller provides to wrap the underlying\n * `docker build` failure in a typed error specific to\n * its call site.\n * @throws Whatever `wrapError` returns when `docker build` exits non-zero.\n */\nexport async function buildDockerImage(\n asset: { source: DockerImageAssetSource },\n cdkOutDir: string,\n tag: string,\n options: {\n platform?: string;\n wrapError: (stderr: string) => Error;\n }\n): Promise<void> {\n const logger = getLogger().child('docker-build');\n const args: string[] = ['build', '-t', tag];\n\n if (options.platform) {\n args.push('--platform', options.platform);\n }\n\n // Dockerfile\n if (asset.source.dockerFile) {\n args.push('-f', asset.source.dockerFile);\n }\n\n // Build args (order preserved per Object.entries — load-bearing for cache\n // reproducibility across both callers).\n if (asset.source.dockerBuildArgs) {\n for (const [key, value] of Object.entries(asset.source.dockerBuildArgs)) {\n args.push('--build-arg', `${key}=${value}`);\n }\n }\n\n // Build target\n if (asset.source.dockerBuildTarget) {\n args.push('--target', asset.source.dockerBuildTarget);\n }\n\n // Build outputs\n if (asset.source.dockerOutputs) {\n for (const output of asset.source.dockerOutputs) {\n args.push('--output', output);\n }\n }\n\n // Context directory\n const contextDir = `${cdkOutDir}/${asset.source.directory}`;\n args.push(contextDir);\n\n logger.debug(`docker ${args.join(' ')}`);\n\n try {\n await execFileAsync('docker', args, {\n maxBuffer: 50 * 1024 * 1024, // 50MB for build output\n });\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n const stderr = err.stderr || err.message || String(error);\n throw options.wrapError(stderr);\n }\n}\n","import { execFile, spawn } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport {\n ECRClient,\n GetAuthorizationTokenCommand,\n DescribeImagesCommand,\n} from '@aws-sdk/client-ecr';\nimport type { DockerImageAsset } from '../types/assets.js';\nimport { getLogger } from '../utils/logger.js';\nimport { AssetError } from '../utils/error-handler.js';\nimport { buildDockerImage } from './docker-build.js';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Publishes Docker image assets to ECR\n *\n * Handles:\n * - Placeholder resolution\n * - Existence check (skip if already pushed)\n * - docker build with Dockerfile, build args, target\n * - ECR authentication\n * - docker tag + docker push\n */\nexport class DockerAssetPublisher {\n private logger = getLogger().child('DockerAssetPublisher');\n\n /**\n * Publish a Docker image asset to ECR\n */\n async publish(\n assetHash: string,\n asset: DockerImageAsset,\n cdkOutputDir: string,\n accountId: string,\n region: string\n ): Promise<void> {\n for (const [, dest] of Object.entries(asset.destinations)) {\n const repositoryName = this.resolvePlaceholders(dest.repositoryName, accountId, region);\n const imageTag = this.resolvePlaceholders(dest.imageTag, accountId, region);\n const destRegion = dest.region\n ? this.resolvePlaceholders(dest.region, accountId, region)\n : region;\n\n const ecrUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n\n this.logger.debug(`Publishing Docker image ${asset.displayName || assetHash} → ${ecrUri}`);\n\n const client = new ECRClient({ region: destRegion });\n\n try {\n // Check if image already exists\n if (await this.imageExists(client, repositoryName, imageTag)) {\n this.logger.debug(`Image already exists, skipping: ${ecrUri}`);\n continue;\n }\n\n // Build Docker image\n const localTag = `cdkd-asset-${assetHash}`;\n await this.buildImage(asset, cdkOutputDir, localTag);\n\n // Authenticate with ECR\n await this.ecrLogin(client, accountId, destRegion);\n\n // Tag and push\n const fullUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n await this.tagImage(localTag, fullUri);\n await this.pushImage(fullUri);\n\n this.logger.debug(`✅ Published: ${ecrUri}`);\n } finally {\n client.destroy();\n }\n }\n }\n\n /**\n * Build a Docker image (public, used by WorkGraph asset-build nodes)\n */\n async build(asset: DockerImageAsset, cdkOutputDir: string, localTag: string): Promise<void> {\n await this.buildImage(asset, cdkOutputDir, localTag);\n }\n\n /**\n * Push a pre-built Docker image to ECR (public, used by WorkGraph asset-publish nodes)\n */\n async push(\n asset: DockerImageAsset,\n accountId: string,\n region: string,\n localTag: string\n ): Promise<void> {\n for (const [, dest] of Object.entries(asset.destinations)) {\n const repositoryName = this.resolvePlaceholders(dest.repositoryName, accountId, region);\n const imageTag = this.resolvePlaceholders(dest.imageTag, accountId, region);\n const destRegion = dest.region\n ? this.resolvePlaceholders(dest.region, accountId, region)\n : region;\n\n const ecrUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n\n const client = new ECRClient({ region: destRegion });\n\n try {\n if (await this.imageExists(client, repositoryName, imageTag)) {\n this.logger.debug(`Image already exists, skipping: ${ecrUri}`);\n continue;\n }\n\n await this.ecrLogin(client, accountId, destRegion);\n\n const fullUri = `${accountId}.dkr.ecr.${destRegion}.amazonaws.com/${repositoryName}:${imageTag}`;\n await this.tagImage(localTag, fullUri);\n await this.pushImage(fullUri);\n\n this.logger.debug(`✅ Published: ${ecrUri}`);\n } finally {\n client.destroy();\n }\n }\n }\n\n /**\n * Check if image exists in ECR\n */\n private async imageExists(\n client: ECRClient,\n repositoryName: string,\n imageTag: string\n ): Promise<boolean> {\n try {\n const response = await client.send(\n new DescribeImagesCommand({\n repositoryName,\n imageIds: [{ imageTag }],\n })\n );\n return (response.imageDetails?.length ?? 0) > 0;\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ImageNotFoundException' || err.name === 'RepositoryNotFoundException') {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Build Docker image — delegates to the shared `buildDockerImage`\n * helper so this code path stays in sync with `cdkd local invoke`'s\n * container-Lambda build path. `--platform` is currently not threaded\n * through here (publish-assets has no Architectures hint to consult);\n * a follow-up can lift this once the asset manifest carries a\n * platform field.\n */\n private async buildImage(\n asset: DockerImageAsset,\n cdkOutputDir: string,\n tag: string\n ): Promise<void> {\n await buildDockerImage(asset, cdkOutputDir, tag, {\n wrapError: (stderr) => new AssetError(`Docker build failed: ${stderr}`),\n });\n }\n\n /**\n * Authenticate with ECR\n */\n private async ecrLogin(client: ECRClient, accountId: string, region: string): Promise<void> {\n const response = await client.send(new GetAuthorizationTokenCommand({}));\n const authData = response.authorizationData?.[0];\n\n if (!authData?.authorizationToken) {\n throw new AssetError('Failed to get ECR authorization token');\n }\n\n const token = Buffer.from(authData.authorizationToken, 'base64').toString();\n const [username, password] = token.split(':');\n const endpoint =\n authData.proxyEndpoint || `https://${accountId}.dkr.ecr.${region}.amazonaws.com`;\n\n await new Promise<void>((resolve, reject) => {\n const proc = spawn(\n 'docker',\n ['login', '--username', username!, '--password-stdin', endpoint],\n {\n stdio: ['pipe', 'pipe', 'pipe'],\n }\n );\n\n let stderr = '';\n proc.stderr?.on('data', (data: Buffer) => {\n stderr += data.toString();\n });\n\n proc.on('close', (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new AssetError(`ECR login failed: ${stderr.trim()}`));\n }\n });\n\n proc.on('error', (err) => {\n reject(new AssetError(`ECR login failed: ${err.message}`));\n });\n\n // Write password to stdin and close\n proc.stdin?.write(password);\n proc.stdin?.end();\n });\n }\n\n /**\n * Tag Docker image\n */\n private async tagImage(source: string, target: string): Promise<void> {\n await execFileAsync('docker', ['tag', source, target]);\n }\n\n /**\n * Push Docker image\n */\n private async pushImage(uri: string): Promise<void> {\n this.logger.debug(`Pushing: ${uri}`);\n try {\n await execFileAsync('docker', ['push', uri], {\n maxBuffer: 50 * 1024 * 1024,\n });\n } catch (error) {\n const err = error as { stderr?: string; message?: string };\n throw new AssetError(`Docker push failed: ${err.stderr || err.message || String(error)}`);\n }\n }\n\n /**\n * Replace placeholders in destination values\n */\n private resolvePlaceholders(\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","import { getLogger } from '../utils/logger.js';\n\n/**\n * Node types in the work graph\n */\nexport type WorkNodeType = 'asset-build' | 'asset-publish' | 'stack';\n\n/**\n * Node states\n */\nexport type NodeState = 'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'skipped';\n\n/**\n * A node in the work graph\n */\nexport interface WorkNode {\n id: string;\n type: WorkNodeType;\n dependencies: Set<string>;\n state: NodeState;\n /** Custom data attached to this node */\n data: unknown;\n}\n\n/**\n * Concurrency limits per node type\n */\nexport interface WorkGraphConcurrency {\n 'asset-build': number;\n 'asset-publish': number;\n stack: number;\n}\n\n/**\n * Work graph for orchestrating asset building, publishing and stack deployments.\n *\n * Manages a DAG of nodes with dependencies, executing them in parallel\n * with per-type concurrency limits. Nodes become ready when all their\n * dependencies are completed.\n *\n * Node types:\n * - asset-build: Docker image build (CPU/memory bound)\n * - asset-publish: S3 upload or ECR push (I/O bound)\n * - stack: Stack deployment (depends on its asset nodes)\n *\n * Dependencies:\n * - File assets: asset-publish → stack\n * - Docker assets: asset-build → asset-publish → stack\n * - Inter-stack: stack → stack (CDK dependency order)\n */\nexport class WorkGraph {\n private nodes = new Map<string, WorkNode>();\n private logger = getLogger().child('WorkGraph');\n\n addNode(node: WorkNode): void {\n this.nodes.set(node.id, node);\n }\n\n /**\n * Execute all nodes in the graph with bounded concurrency per type.\n */\n async execute(\n concurrency: WorkGraphConcurrency,\n fn: (node: WorkNode) => Promise<void>\n ): Promise<void> {\n const active: Record<WorkNodeType, number> = { 'asset-build': 0, 'asset-publish': 0, stack: 0 };\n const errors: Array<{ nodeId: string; error: unknown }> = [];\n\n return new Promise<void>((resolve, reject) => {\n const dispatch = (): void => {\n // Find ready nodes: pending with all dependencies completed\n const ready: WorkNode[] = [];\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const depsReady = [...node.dependencies].every((depId) => {\n const dep = this.nodes.get(depId);\n return dep && dep.state === 'completed';\n });\n if (depsReady) {\n ready.push(node);\n }\n }\n\n // Skip nodes with failed dependencies\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const hasFailedDep = [...node.dependencies].some((depId) => {\n const dep = this.nodes.get(depId);\n return dep && (dep.state === 'failed' || dep.state === 'skipped');\n });\n if (hasFailedDep) {\n node.state = 'skipped';\n this.logger.debug(`Skipped ${node.id}: dependency failed`);\n }\n }\n\n // Start eligible nodes\n for (const node of ready) {\n if (active[node.type] >= concurrency[node.type]) continue;\n\n node.state = 'running';\n active[node.type]++;\n\n fn(node)\n .then(() => {\n node.state = 'completed';\n })\n .catch((error) => {\n node.state = 'failed';\n errors.push({ nodeId: node.id, error });\n this.logger.error(\n `Failed: ${node.id}: ${error instanceof Error ? error.message : String(error)}`\n );\n })\n .finally(() => {\n active[node.type]--;\n dispatch(); // Re-evaluate after each completion\n });\n }\n\n // Check termination\n const totalActive = active['asset-build'] + active['asset-publish'] + active['stack'];\n if (totalActive === 0) {\n const pending = [...this.nodes.values()].filter(\n (n) => n.state === 'pending' || n.state === 'queued'\n );\n\n if (pending.length > 0) {\n reject(\n new Error(\n `Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies`\n )\n );\n return;\n }\n\n if (errors.length > 0) {\n const skippedCount = [...this.nodes.values()].filter(\n (n) => n.state === 'skipped'\n ).length;\n const msg = errors\n .map(\n (e) =>\n ` - ${e.nodeId}: ${e.error instanceof Error ? e.error.message : String(e.error)}`\n )\n .join('\\n');\n reject(\n new Error(\n `${errors.length} node(s) failed${skippedCount > 0 ? `, ${skippedCount} skipped` : ''}:\\n${msg}`\n )\n );\n return;\n }\n\n resolve();\n }\n };\n\n dispatch();\n });\n }\n\n /**\n * Get summary of node counts by type\n */\n summary(): Record<WorkNodeType, number> {\n const counts: Record<WorkNodeType, number> = { 'asset-build': 0, 'asset-publish': 0, stack: 0 };\n for (const node of this.nodes.values()) {\n counts[node.type]++;\n }\n return counts;\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 { readFileSync } from 'node:fs';\nimport { FileAssetPublisher } from './file-asset-publisher.js';\nimport { DockerAssetPublisher } from './docker-asset-publisher.js';\nimport type { AssetManifest, FileAsset, DockerImageAsset } from '../types/assets.js';\nimport { WorkGraph, type WorkNode } from '../deployment/work-graph.js';\nimport { getLogger } from '../utils/logger.js';\nimport { AssetError } from '../utils/error-handler.js';\nimport { stringifyValue } from '../utils/stringify.js';\n\n/**\n * Data attached to a file asset-publish node\n */\nexport interface FileAssetNodeData {\n kind: 'file';\n hash: string;\n asset: FileAsset;\n cdkOutputDir: string;\n accountId: string;\n region: string;\n profile?: string;\n}\n\n/**\n * Data attached to a Docker asset-build node\n */\nexport interface DockerBuildNodeData {\n kind: 'docker-build';\n hash: string;\n asset: DockerImageAsset;\n cdkOutputDir: string;\n localTag: string;\n}\n\n/**\n * Data attached to a Docker asset-publish node\n */\nexport interface DockerPublishNodeData {\n kind: 'docker-publish';\n asset: DockerImageAsset;\n accountId: string;\n region: string;\n localTag: string;\n}\n\nexport type AssetNodeData = FileAssetNodeData | DockerBuildNodeData | DockerPublishNodeData;\n\n/**\n * Asset publishing options\n */\nexport interface AssetPublisherOptions {\n /** AWS profile to use */\n profile?: string;\n\n /** AWS region */\n region?: string;\n\n /** AWS account ID */\n accountId?: string;\n\n /** Concurrency for asset publishing (S3 uploads + ECR push). Default: 8 */\n assetPublishConcurrency?: number;\n\n /** Concurrency for Docker image builds. Default: 4 */\n imageBuildConcurrency?: number;\n}\n\n/**\n * Asset publisher\n *\n * Orchestrates file and Docker image asset publishing via WorkGraph.\n * - File assets: single asset-publish node (S3 upload)\n * - Docker assets: asset-build node → asset-publish node (build then push)\n */\nexport class AssetPublisher {\n private logger = getLogger().child('AssetPublisher');\n private filePublisher = new FileAssetPublisher();\n private dockerPublisher = new DockerAssetPublisher();\n\n /**\n * Add asset nodes from a manifest to a WorkGraph.\n * Returns the node IDs that stack deploy should depend on.\n */\n addAssetsToGraph(\n graph: WorkGraph,\n manifestPath: string,\n options: { accountId: string; region: string; profile?: string; nodePrefix?: string }\n ): string[] {\n const content = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as AssetManifest;\n const cdkOutputDir = manifestPath.replace(/\\/[^/]+$/, '');\n const prefix = options.nodePrefix || '';\n const nodeIds: string[] = [];\n\n // File assets: single publish node\n const fileAssets = Object.entries(manifest.files || {}).filter(\n ([, asset]) =>\n !asset.source.path.endsWith('.json') && !asset.source.path.endsWith('.template.json')\n );\n for (const [hash, asset] of fileAssets) {\n const nodeId = `asset-publish:${prefix}file:${hash}`;\n graph.addNode({\n id: nodeId,\n type: 'asset-publish',\n dependencies: new Set(),\n state: 'pending',\n data: {\n kind: 'file',\n hash,\n asset,\n cdkOutputDir,\n accountId: options.accountId,\n region: options.region,\n ...(options.profile && { profile: options.profile }),\n } satisfies FileAssetNodeData,\n });\n nodeIds.push(nodeId);\n }\n\n // Docker assets: build node → publish node\n for (const [hash, asset] of Object.entries(manifest.dockerImages || {})) {\n const localTag = `cdkd-asset-${hash}`;\n const buildNodeId = `asset-build:${prefix}docker:${hash}`;\n const publishNodeId = `asset-publish:${prefix}docker:${hash}`;\n\n graph.addNode({\n id: buildNodeId,\n type: 'asset-build',\n dependencies: new Set(),\n state: 'pending',\n data: {\n kind: 'docker-build',\n hash,\n asset,\n cdkOutputDir,\n localTag,\n } satisfies DockerBuildNodeData,\n });\n\n graph.addNode({\n id: publishNodeId,\n type: 'asset-publish',\n dependencies: new Set([buildNodeId]),\n state: 'pending',\n data: {\n kind: 'docker-publish',\n asset,\n accountId: options.accountId,\n region: options.region,\n localTag,\n } satisfies DockerPublishNodeData,\n });\n\n // Stack depends on the publish node (not build)\n nodeIds.push(publishNodeId);\n }\n\n this.logger.debug(\n `Added ${fileAssets.length} file + ${Object.keys(manifest.dockerImages || {}).length} docker asset(s) to graph`\n );\n\n return nodeIds;\n }\n\n /**\n * Execute an asset node (build or publish)\n */\n async executeNode(node: WorkNode): Promise<void> {\n const data = node.data as AssetNodeData;\n\n if (data.kind === 'file') {\n await this.filePublisher.publish(\n data.hash,\n data.asset,\n data.cdkOutputDir,\n data.accountId,\n data.region,\n data.profile\n );\n } else if (data.kind === 'docker-build') {\n await this.dockerPublisher.build(data.asset, data.cdkOutputDir, data.localTag);\n } else if (data.kind === 'docker-publish') {\n await this.dockerPublisher.push(data.asset, data.accountId, data.region, data.localTag);\n }\n\n this.logger.debug(`✅ ${node.id}`);\n }\n\n /**\n * Publish assets from manifest file (standalone, uses WorkGraph internally)\n */\n async publishFromManifest(\n manifestPath: string,\n options: AssetPublisherOptions = {}\n ): Promise<void> {\n try {\n this.logger.debug('Loading asset manifest:', manifestPath);\n\n const region = options.region || process.env['AWS_REGION'] || 'us-east-1';\n let accountId = options.accountId;\n\n if (!accountId) {\n const { STSClient, GetCallerIdentityCommand } = await import('@aws-sdk/client-sts');\n const stsClient = new STSClient({ region });\n const identity = await stsClient.send(new GetCallerIdentityCommand({}));\n accountId = identity.Account!;\n stsClient.destroy();\n }\n\n const graph = new WorkGraph();\n const nodeIds = this.addAssetsToGraph(graph, manifestPath, {\n accountId,\n region,\n ...(options.profile && { profile: options.profile }),\n });\n\n if (nodeIds.length === 0) {\n this.logger.debug('No assets to publish');\n return;\n }\n\n await graph.execute(\n {\n 'asset-build': options.imageBuildConcurrency ?? 4,\n 'asset-publish': options.assetPublishConcurrency ?? 8,\n stack: 0,\n },\n (node) => this.executeNode(node)\n );\n\n this.logger.debug('✅ All assets published successfully');\n } catch (error) {\n if (error instanceof AssetError) {\n throw error;\n }\n const err = error as Record<string, unknown>;\n const message = stringifyValue(err['message'] || err['name'] || error);\n const code = stringifyValue(err['Code'] || err['code'] || err['name'] || '');\n const detail = code ? `${code}: ${message}` : message;\n throw new AssetError(\n `Asset publishing failed: ${detail}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Check if assets need to be published\n */\n hasAssets(manifestPath: string): boolean {\n try {\n const content = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(content) as AssetManifest;\n const fileCount = Object.keys(manifest.files || {}).length;\n const dockerCount = Object.keys(manifest.dockerImages || {}).length;\n return fileCount + dockerCount > 0;\n } catch {\n this.logger.warn('Failed to check assets');\n return false;\n }\n }\n}\n","/**\n * Schema versions for cdkd state.json.\n *\n * - 1 — legacy layout: `s3://{bucket}/cdkd/{stackName}/state.json` (pre PR 1).\n * - 2 — region-prefixed layout: `s3://{bucket}/cdkd/{stackName}/{region}/state.json`.\n * - 3 — adds `ResourceState.observedProperties` (AWS-current snapshot\n * captured at deploy/import time, used as the drift comparator's\n * baseline). Layout is the same as v2; only the resource-level shape\n * grew. v2 readers see v3 as `version: 3` and fail clearly.\n * - 4 — adds `StackState.imports` (the set of `Fn::ImportValue` references\n * this stack resolved during its last deploy). Consumed by\n * `cdkd destroy` to refuse deleting a producer while a consumer still\n * references its outputs (strong reference, matches CloudFormation).\n * Layout is the same as v3; only the stack-level shape grew. v3\n * readers see v4 as `version: 4` and fail clearly.\n * - 5 — adds `ResourceState.deletionPolicy` and `updateReplacePolicy`, the\n * CloudFormation template attributes recorded at deploy time. cdkd\n * compares these against the next deploy's template to detect\n * attribute-only changes (e.g. `RemovalPolicy.DESTROY` removed →\n * `DeletionPolicy: Retain` now in template), which previously fell\n * through DiffCalculator as `No changes detected`. Layout is the same\n * as v4; only the resource-level shape grew. v4 readers see v5 as\n * `version: 5` and fail clearly.\n *\n * cdkd readers handle every prior version. Writers always emit\n * `STATE_SCHEMA_VERSION_CURRENT`. An older cdkd binary that only knows an\n * earlier version will fail with a clear error when it encounters a higher\n * version, rather than silently mishandling the new format.\n */\nexport type StateSchemaVersion = 1 | 2 | 3 | 4 | 5;\nexport const STATE_SCHEMA_VERSION_LEGACY: StateSchemaVersion = 1;\nexport const STATE_SCHEMA_VERSION_CURRENT: StateSchemaVersion = 5;\n\n/**\n * Every schema version this binary can read. Writers always emit\n * `STATE_SCHEMA_VERSION_CURRENT`; older versions are accepted for\n * forward-migration, and an unknown / future version triggers an explicit\n * \"upgrade cdkd\" error in the parser.\n */\nexport const STATE_SCHEMA_VERSIONS_READABLE: readonly StateSchemaVersion[] = [1, 2, 3, 4, 5];\n\n/**\n * One `Fn::ImportValue` reference recorded during a consumer stack's\n * deploy. Persisted in `StackState.imports` so `cdkd destroy` can refuse\n * to delete the producer while the consumer still references its outputs\n * (strong reference, matches CloudFormation behavior).\n *\n * Only `Fn::ImportValue` populates this — `Fn::GetStackOutput` is a weak\n * reference by design (cdkd-specific) and intentionally does NOT record\n * an entry here so the producer stays deletable independently of consumers.\n */\nexport interface StateImportEntry {\n /** The producer stack whose Output `Export.Name` was imported. */\n sourceStack: string;\n /**\n * The producer's region. Required so destroy-time strong-ref checks\n * can scan the producer's exact `state.json` key (cdkd state is keyed\n * by `(stackName, region)` since schema v2).\n */\n sourceRegion: string;\n /** The CloudFormation Output `Export.Name` that was imported. */\n exportName: string;\n}\n\n/**\n * Stack state stored in S3\n */\nexport interface StackState {\n /**\n * Schema version. `1` is the legacy unversioned-key layout, `2` is the\n * region-prefixed layout. New writes always use the current version.\n */\n version: StateSchemaVersion;\n\n /** Stack name */\n stackName: string;\n\n /**\n * Target region for this stack. Required on `version: 2` since the region\n * is part of the S3 key. Optional on `version: 1` for backwards compat.\n */\n region?: string;\n\n /** Resources in the stack */\n resources: Record<string, ResourceState>;\n\n /** Stack outputs (values can be any type) */\n outputs: Record<string, unknown>;\n\n /**\n * `Fn::ImportValue` references this stack resolved during its last\n * successful deploy. Populated on schema v4+; absent (or undefined)\n * on state written by an older cdkd binary, in which case the\n * destroy-time strong-reference check degrades gracefully (no\n * recorded imports = no consumers known = destroy proceeds). The\n * next deploy of an upgraded stack repopulates the field.\n */\n imports?: StateImportEntry[];\n\n /** Last modification timestamp (Unix milliseconds) */\n lastModified: number;\n}\n\n/**\n * Individual resource state\n */\nexport interface ResourceState {\n /** Physical resource ID (ARN, name, etc.) */\n physicalId: string;\n\n /** CloudFormation resource type (e.g., AWS::Lambda::Function) */\n resourceType: string;\n\n /** Resource properties */\n properties: Record<string, unknown>;\n\n /**\n * AWS-current snapshot of this resource's properties as returned by\n * `provider.readCurrentState` immediately after a successful create /\n * update / import. Used as the drift comparator's baseline (instead of\n * `properties`) so console-side changes to keys the user did not\n * template still surface as drift.\n *\n * Optional for backwards compatibility — resources written by an older\n * cdkd binary (v2 state, or v3 state on a provider that does not\n * implement `readCurrentState`) keep this field undefined; the drift\n * command falls back to comparing against `properties` in that case.\n */\n observedProperties?: Record<string, unknown>;\n\n /** Resource attributes for Fn::GetAtt resolution */\n attributes?: Record<string, unknown>;\n\n /** Resource dependencies (logical IDs) for proper deletion order */\n dependencies?: string[];\n\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n\n /**\n * CloudFormation `DeletionPolicy` attribute recorded at deploy time\n * (schema v5+). Compared against the template on the next deploy so an\n * attribute-only change (e.g. `RemovalPolicy.DESTROY` removed →\n * `DeletionPolicy: Retain`) is surfaced as a diff instead of silently\n * being marked `No changes`. Optional for backwards compatibility — v4\n * state writes leave this undefined; the diff comparator treats\n * `undefined` as \"no attribute recorded\" rather than \"Delete\" so the\n * first post-upgrade deploy only fires the diff when the template\n * actually carries the attribute.\n *\n * The `| undefined` is explicit (vs bare `?:`) so a state-update site\n * can spread `{ ...current, deletionPolicy: undefined }` to clear a\n * previously-recorded value when the user removes the attribute from\n * their CDK code; under `exactOptionalPropertyTypes: true` a bare `?:`\n * would reject the literal-undefined assignment.\n */\n deletionPolicy?: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n\n /**\n * CloudFormation `UpdateReplacePolicy` attribute recorded at deploy time\n * (schema v5+). Same semantics as `deletionPolicy` above.\n */\n updateReplacePolicy?: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n}\n\n/**\n * Lock information for stack operations\n */\nexport interface LockInfo {\n /** Lock owner (e.g., username, CI job ID) */\n owner: string;\n\n /** Lock acquisition timestamp (Unix milliseconds) */\n timestamp: number;\n\n /** Lock expiration timestamp (Unix milliseconds) */\n expiresAt: number;\n\n /** Optional operation being performed */\n operation?: string;\n}\n\n/**\n * Change type for resource diff\n */\nexport type ChangeType = 'CREATE' | 'UPDATE' | 'DELETE' | 'NO_CHANGE';\n\n/**\n * Resource change information\n */\nexport interface ResourceChange {\n /** Logical ID from CloudFormation template */\n logicalId: string;\n\n /** Type of change */\n changeType: ChangeType;\n\n /** Resource type */\n resourceType: string;\n\n /** Current properties (for UPDATE/DELETE) */\n currentProperties?: Record<string, unknown>;\n\n /** Desired properties (for CREATE/UPDATE) */\n desiredProperties?: Record<string, unknown>;\n\n /** Property-level changes (for UPDATE) */\n propertyChanges?: PropertyChange[];\n\n /**\n * `DeletionPolicy` / `UpdateReplacePolicy` attribute changes (schema v5+).\n * Populated when the template attribute differs from the value recorded in\n * cdkd state. AWS has no API to mutate these attributes per-resource, so\n * the deploy engine handles the change by updating cdkd state only — no\n * provider call. UPDATE classification still fires when only these change\n * (DiffCalculator does not stay at `NO_CHANGE`), so users see the diff\n * instead of `No changes detected`.\n */\n attributeChanges?: AttributeChange[];\n}\n\n/**\n * Template-level resource attribute change (schema v5+).\n *\n * `DeletionPolicy` / `UpdateReplacePolicy` are CloudFormation template\n * metadata — they have no AWS API per-resource and are mutated through the\n * cdkd state record alone.\n */\nexport interface AttributeChange {\n /** Attribute name: `DeletionPolicy` or `UpdateReplacePolicy`. */\n attribute: 'DeletionPolicy' | 'UpdateReplacePolicy';\n oldValue: string | undefined;\n newValue: string | undefined;\n}\n\n/**\n * Returns true when a recorded `DeletionPolicy` should prevent cdkd from\n * deleting the underlying AWS resource. `Retain` and `RetainExceptOnCreate`\n * both keep the resource around; `Delete` / `Snapshot` / undefined all\n * fall through to the normal delete path. Shared between\n * `runDestroyForStack` (state-only, no template) and `DeployEngine`'s\n * DELETE branch (state-preferred, template-fallback) so the two paths\n * cannot drift on the policy semantics. Lives here (not in\n * deploy-engine or destroy-runner) because both consumers already\n * depend on this module — placing it in either would create a cycle.\n */\nexport function shouldRetainResource(\n deletionPolicy: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined\n): boolean {\n return deletionPolicy === 'Retain' || deletionPolicy === 'RetainExceptOnCreate';\n}\n\n/**\n * Property-level change\n */\nexport interface PropertyChange {\n /** Property path (e.g., \"Code.S3Key\") */\n path: string;\n\n /** Old value */\n oldValue: unknown;\n\n /** New value */\n newValue: unknown;\n\n /** Whether this change requires replacement */\n requiresReplacement: boolean;\n}\n","import {\n S3Client,\n GetObjectCommand,\n PutObjectCommand,\n DeleteObjectCommand,\n HeadBucketCommand,\n HeadObjectCommand,\n ListObjectsV2Command,\n NoSuchKey,\n} from '@aws-sdk/client-s3';\nimport {\n STATE_SCHEMA_VERSION_CURRENT,\n STATE_SCHEMA_VERSIONS_READABLE,\n type StackState,\n} from '../types/state.js';\nimport type { StateBackendConfig } from '../types/config.js';\nimport { getLogger } from '../utils/logger.js';\nimport { StateError, normalizeAwsError } from '../utils/error-handler.js';\nimport { resolveBucketRegion } from '../utils/aws-region-resolver.js';\n\n/**\n * Identifier of a state record. The legacy layout (`version: 1`) didn't have\n * region in the S3 key, so reads from the legacy key carry `region:\n * undefined`.\n */\nexport interface StackStateRef {\n stackName: string;\n /** Region of the state. `undefined` ONLY for legacy `version: 1` records. */\n region?: string;\n}\n\n/**\n * The `version: 1` legacy state key under the `cdkd/` prefix. Two layers\n * deep — split off into a constant so call sites can clearly distinguish\n * \"two-segment legacy key\" from \"three-segment new key\".\n */\nconst LEGACY_KEY_DEPTH = 2;\n/** The `version: 2` region-prefixed key. */\nconst NEW_KEY_DEPTH = 3;\n\n/**\n * Options used to reconstruct the S3Client if the bucket lives in a region\n * different from the one the initial client was built for.\n *\n * Mirrors {@link AwsClientConfig} from `aws-clients.ts` but kept local so\n * the state backend doesn't depend on the CLI-side AwsClients wrapper.\n */\nexport interface S3ClientOptions {\n region?: string;\n profile?: string;\n credentials?: {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n };\n}\n\n/**\n * S3-based state backend using conditional writes for optimistic locking.\n *\n * State keys are region-scoped (`{prefix}/{stackName}/{region}/state.json`)\n * to prevent two regions of the same stackName from overwriting each other's\n * state. Legacy `{prefix}/{stackName}/state.json` keys (schema `version: 1`)\n * are still readable; the next `saveState` for that stack auto-migrates by\n * writing the new key and deleting the legacy one.\n *\n * The state bucket can live in a different AWS region from the rest of the\n * cdkd CLI's resource provisioning. Before the first state operation, this\n * backend resolves the bucket's actual region via `GetBucketLocation` and,\n * if it differs from the client's configured region, rebuilds the S3Client\n * for that region. Provisioning clients are unaffected — only the\n * state-bucket S3 client is region-corrected.\n */\nexport class S3StateBackend {\n private logger = getLogger().child('S3StateBackend');\n private s3Client: S3Client;\n private config: StateBackendConfig;\n private clientOpts: S3ClientOptions;\n private clientResolved = false;\n private resolveInFlight: Promise<void> | null = null;\n\n constructor(s3Client: S3Client, config: StateBackendConfig, clientOpts: S3ClientOptions = {}) {\n this.s3Client = s3Client;\n this.config = config;\n this.clientOpts = clientOpts;\n }\n\n /**\n * Get the new (region-scoped) S3 key for a stack's state file.\n */\n private getStateKey(stackName: string, region: string): string {\n return `${this.config.prefix}/${stackName}/${region}/state.json`;\n }\n\n /**\n * Get the legacy (pre-region-prefix) S3 key for a stack's state file.\n * Used for backwards-compatible reads and for the migration delete.\n */\n private getLegacyStateKey(stackName: string): string {\n return `${this.config.prefix}/${stackName}/state.json`;\n }\n\n /**\n * Resolve the state bucket's actual region and, if it differs from the\n * client's currently-configured region, replace the S3Client with one\n * pointed at the bucket's region.\n *\n * This is idempotent: subsequent calls return immediately. Concurrent\n * callers (e.g. when several public methods race during a parallel deploy)\n * share a single in-flight resolution promise so we never issue more than\n * one `GetBucketLocation` per backend.\n *\n * Errors from `GetBucketLocation` are deliberately swallowed by\n * `resolveBucketRegion` — the resolver returns `fallbackRegion` so the\n * caller can surface the more actionable downstream error (e.g. the\n * `HeadBucket` 404 routed via `normalizeAwsError`).\n */\n private async ensureClientForBucket(): Promise<void> {\n if (this.clientResolved) return;\n if (this.resolveInFlight) return this.resolveInFlight;\n\n this.resolveInFlight = (async (): Promise<void> => {\n try {\n const currentRegion = await this.s3Client.config.region();\n const fallbackRegion = typeof currentRegion === 'string' ? currentRegion : undefined;\n const bucketRegion = await resolveBucketRegion(this.config.bucket, {\n ...(this.clientOpts.profile && { profile: this.clientOpts.profile }),\n ...(this.clientOpts.credentials && { credentials: this.clientOpts.credentials }),\n ...(fallbackRegion && { fallbackRegion }),\n });\n\n if (bucketRegion !== currentRegion) {\n this.logger.debug(\n `State bucket '${this.config.bucket}' is in '${bucketRegion}' (client was '${currentRegion}'); rebuilding S3 client.`\n );\n const oldClient = this.s3Client;\n this.s3Client = new S3Client({\n region: bucketRegion,\n ...(this.clientOpts.profile && { profile: this.clientOpts.profile }),\n ...(this.clientOpts.credentials && { credentials: this.clientOpts.credentials }),\n // Suppress \"Are you using a Stream of unknown length\" warning,\n // matching the suppression in AwsClients.\n logger: { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} },\n });\n oldClient.destroy();\n }\n this.clientResolved = true;\n } finally {\n this.resolveInFlight = null;\n }\n })();\n\n return this.resolveInFlight;\n }\n\n /**\n * Verify that the configured state bucket exists.\n *\n * Called early in deploy/destroy to fail fast before expensive work\n * (asset publishing, Docker builds) runs against a missing bucket.\n *\n * Errors are routed through {@link normalizeAwsError} so the AWS SDK v3\n * synthetic `UnknownError` (e.g. cross-region HEAD) becomes a concrete\n * \"Bucket does not exist\" / \"Access denied\" / \"different region\" message.\n */\n async verifyBucketExists(): Promise<void> {\n await this.ensureClientForBucket();\n try {\n await this.s3Client.send(new HeadBucketCommand({ Bucket: this.config.bucket }));\n } catch (error) {\n const name = (error as { name?: string }).name;\n if (name === 'NotFound' || name === 'NoSuchBucket') {\n throw new StateError(\n `State bucket '${this.config.bucket}' does not exist. ` +\n `Run 'cdkd bootstrap' to create it, or specify an existing bucket via ` +\n `--state-bucket, CDKD_STATE_BUCKET, or cdk.json context.cdkd.stateBucket.`\n );\n }\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'HeadBucket',\n });\n throw new StateError(\n `Failed to verify state bucket '${this.config.bucket}': ${normalized.message}`,\n normalized\n );\n }\n }\n\n /**\n * Check if state exists for a stack in the given region.\n *\n * Returns true for either layout: the new region-scoped key, or the legacy\n * key when its embedded `region` matches the requested region. This lets\n * `cdkd state orphan <stack> --region X` and `cdkd destroy <stack>` see legacy\n * state without forcing a write-through migration first.\n */\n async stateExists(stackName: string, region: string): Promise<boolean> {\n await this.ensureClientForBucket();\n const newKey = this.getStateKey(stackName, region);\n\n if (await this.headObject(newKey)) {\n return true;\n }\n\n return this.legacyMatchesRegion(stackName, region);\n }\n\n /**\n * Get state for a stack, transparently falling back to the legacy key.\n *\n * Lookup order:\n * 1. `{prefix}/{stackName}/{region}/state.json` (current `version: 2` key).\n * 2. `{prefix}/{stackName}/state.json` (legacy `version: 1` key) — only\n * accepted if its embedded `region` matches the requested region.\n *\n * When a legacy hit is returned, `migrationPending` is `true`. Callers that\n * subsequently `saveState` automatically migrate by writing the new key and\n * deleting the legacy one (see `saveState`'s `legacyMigration` argument).\n *\n * Note: S3 returns ETag with surrounding quotes (e.g., `\"abc123\"`). We\n * preserve the quotes — they are required for `IfMatch` conditions.\n */\n async getState(\n stackName: string,\n region: string\n ): Promise<{ state: StackState; etag: string; migrationPending?: boolean } | null> {\n await this.ensureClientForBucket();\n const newKey = this.getStateKey(stackName, region);\n\n // 1. Try new region-scoped key first.\n try {\n this.logger.debug(`Getting state for stack: ${stackName} (${region})`);\n\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: newKey,\n })\n );\n\n if (!response.Body) {\n throw new StateError(`State file for stack '${stackName}' (${region}) has no body`);\n }\n if (!response.ETag) {\n throw new StateError(`State file for stack '${stackName}' (${region}) has no ETag`);\n }\n\n const bodyString = await response.Body.transformToString();\n const state = this.parseStateBody(bodyString, stackName);\n this.logger.debug(`Retrieved state: ${stackName} (${region}), ETag: ${response.ETag}`);\n return { state, etag: response.ETag };\n } catch (error) {\n if (!isNoSuchKey(error)) {\n if (error instanceof StateError) throw error;\n throw new StateError(\n `Failed to get state for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n this.logger.debug(`No state at new key for stack: ${stackName} (${region})`);\n }\n\n // 2. Fall back to legacy key when it exists AND its region matches.\n const legacy = await this.tryGetLegacy(stackName, region);\n if (legacy) {\n this.logger.warn(\n `Loaded legacy state for stack '${stackName}' from '${this.getLegacyStateKey(stackName)}'. ` +\n `It will be migrated to the region-scoped layout on next save.`\n );\n return { ...legacy, migrationPending: true };\n }\n\n return null;\n }\n\n /**\n * Save state for a stack with optimistic locking.\n *\n * Always writes to the new region-scoped key. The state body is rewritten\n * with `version: 2` and the supplied region.\n *\n * If the caller observed `migrationPending: true` from `getState`, it\n * should pass the legacy ETag back via `expectedEtag` AND set\n * `migrateLegacy: true`. After the new key is written successfully, the\n * legacy key is deleted to complete migration. The legacy delete is a\n * best-effort follow-up — a failure is logged but does not unwind the new\n * write.\n *\n * @param stackName Stack name\n * @param region Target region (load-bearing — part of the S3 key)\n * @param state State to save\n * @param options Optimistic-lock ETag + legacy-migration flag\n * @returns New ETag (with quotes, e.g., `\"abc123\"`)\n */\n async saveState(\n stackName: string,\n region: string,\n state: StackState,\n options: { expectedEtag?: string; migrateLegacy?: boolean } = {}\n ): Promise<string> {\n await this.ensureClientForBucket();\n const newKey = this.getStateKey(stackName, region);\n const { expectedEtag, migrateLegacy } = options;\n\n // Normalize the body: schema version + region are load-bearing on disk.\n const body: StackState = {\n ...state,\n version: STATE_SCHEMA_VERSION_CURRENT,\n stackName,\n region,\n };\n\n try {\n this.logger.debug(\n `Saving state: ${stackName} (${region})${expectedEtag ? `, expected ETag: ${expectedEtag}` : ''}`\n );\n\n const bodyString = JSON.stringify(body, null, 2);\n const response = await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.config.bucket,\n Key: newKey,\n Body: bodyString,\n ContentLength: Buffer.byteLength(bodyString),\n ContentType: 'application/json',\n // The legacy ETag is for a different key; only forward it when we're\n // updating in-place at the new key.\n ...(!migrateLegacy && expectedEtag && { IfMatch: expectedEtag }),\n })\n );\n\n if (!response.ETag) {\n throw new StateError(\n `No ETag returned after saving state for stack '${stackName}' (${region})`\n );\n }\n this.logger.debug(`State saved: ${stackName} (${region}), new ETag: ${response.ETag}`);\n\n // Migration tail: best-effort delete of the legacy key. We don't fail\n // the save if this errors — the new key is the source of truth and a\n // residual legacy key is recoverable (next call will migrate again).\n if (migrateLegacy) {\n try {\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n this.logger.info(\n `Migrated state for stack '${stackName}' to region-scoped layout (${region})`\n );\n } catch (deleteError) {\n this.logger.warn(\n `Migrated stack '${stackName}' to new key, but failed to delete legacy key: ` +\n `${deleteError instanceof Error ? deleteError.message : String(deleteError)}`\n );\n }\n }\n\n return response.ETag;\n } catch (error) {\n if ((error as { name: string }).name === 'PreconditionFailed') {\n throw new StateError(\n `State has been modified by another process. Expected ETag: ${expectedEtag}, but state has changed.`\n );\n }\n\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'PutObject',\n });\n throw new StateError(\n `Failed to save state for stack '${stackName}' (${region}): ${normalized.message}`,\n normalized\n );\n }\n }\n\n /**\n * Delete state for a stack in the given region.\n *\n * Removes both the new key and the legacy key (if present). Legacy removal\n * is region-conditional: a legacy state file with a different `region`\n * field is left alone.\n */\n async deleteState(stackName: string, region: string): Promise<void> {\n await this.ensureClientForBucket();\n try {\n this.logger.debug(`Deleting state: ${stackName} (${region})`);\n\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getStateKey(stackName, region),\n })\n );\n\n // Sweep the legacy key only if it belongs to the same region.\n if (await this.legacyMatchesRegion(stackName, region)) {\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n this.logger.debug(`Deleted legacy state for stack: ${stackName}`);\n }\n\n this.logger.debug(`State deleted: ${stackName} (${region})`);\n } catch (error) {\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'DeleteObject',\n });\n throw new StateError(\n `Failed to delete state for stack '${stackName}' (${region}): ${normalized.message}`,\n normalized\n );\n }\n }\n\n /**\n * List all stacks with state in the bucket.\n *\n * Returns one `{stackName, region}` pair per state file. Both layouts\n * are enumerated:\n *\n * - `{prefix}/{stackName}/{region}/state.json` (new) — `region` is the\n * path segment.\n * - `{prefix}/{stackName}/state.json` (legacy) — `region` is read from the\n * state body when present, otherwise `undefined`.\n *\n * Pairs are deduplicated by `(stackName, region)` so a stack mid-migration\n * shows up exactly once.\n */\n async listStacks(): Promise<StackStateRef[]> {\n await this.ensureClientForBucket();\n try {\n this.logger.debug('Listing all stacks');\n\n const prefix = `${this.config.prefix}/`;\n const refs: StackStateRef[] = [];\n const seen = new Set<string>();\n let continuationToken: string | undefined;\n\n do {\n const response = await this.s3Client.send(\n new ListObjectsV2Command({\n Bucket: this.config.bucket,\n Prefix: prefix,\n ...(continuationToken && { ContinuationToken: continuationToken }),\n })\n );\n\n for (const obj of response.Contents ?? []) {\n const key = obj.Key;\n if (!key) continue;\n if (!key.endsWith('/state.json')) continue;\n\n const rest = key.slice(prefix.length);\n const segments = rest.split('/');\n\n // New key: {stackName}/{region}/state.json\n if (segments.length === NEW_KEY_DEPTH) {\n const [stackName, region] = segments;\n if (!stackName || !region) continue;\n const dedupeKey = `${stackName}\\0${region}`;\n if (!seen.has(dedupeKey)) {\n seen.add(dedupeKey);\n refs.push({ stackName, region });\n }\n continue;\n }\n\n // Legacy key: {stackName}/state.json\n if (segments.length === LEGACY_KEY_DEPTH) {\n const [stackName] = segments;\n if (!stackName) continue;\n const region = await this.readLegacyRegion(stackName);\n const dedupeKey = `${stackName}\\0${region ?? ''}`;\n if (!seen.has(dedupeKey)) {\n seen.add(dedupeKey);\n refs.push({ stackName, ...(region ? { region } : {}) });\n }\n }\n }\n\n continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;\n } while (continuationToken);\n\n this.logger.debug(`Found ${refs.length} stack(s) across regions`);\n return refs;\n } catch (error) {\n const normalized = normalizeAwsError(error, {\n bucket: this.config.bucket,\n operation: 'ListObjectsV2',\n });\n throw new StateError(`Failed to list stacks: ${normalized.message}`, normalized);\n }\n }\n\n /**\n * HeadObject probe — returns true on 200, false on NotFound. Other errors\n * propagate so we don't accidentally swallow IAM denials.\n */\n private async headObject(key: string): Promise<boolean> {\n try {\n await this.s3Client.send(\n new HeadObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n return true;\n } catch (error) {\n if (isNoSuchKey(error) || (error as { name?: string }).name === 'NotFound') {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Read the legacy state's `region` field. Used for region matching during\n * `stateExists` / `deleteState` and for assigning a region to legacy\n * entries during `listStacks`.\n */\n private async readLegacyRegion(stackName: string): Promise<string | undefined> {\n try {\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n if (!response.Body) return undefined;\n const bodyString = await response.Body.transformToString();\n const state = JSON.parse(bodyString) as Partial<StackState>;\n return typeof state.region === 'string' ? state.region : undefined;\n } catch (error) {\n if (isNoSuchKey(error)) return undefined;\n // Don't fail the whole list on a single bad legacy file — log & skip.\n this.logger.debug(\n `Could not read legacy state region for '${stackName}': ${error instanceof Error ? error.message : String(error)}`\n );\n return undefined;\n }\n }\n\n private async legacyMatchesRegion(stackName: string, region: string): Promise<boolean> {\n const legacyRegion = await this.readLegacyRegion(stackName);\n return legacyRegion === region;\n }\n\n /**\n * Try to read the legacy `version: 1` state. Returns null when the legacy\n * key is missing or its embedded region does not match the caller's region.\n */\n private async tryGetLegacy(\n stackName: string,\n region: string\n ): Promise<{ state: StackState; etag: string } | null> {\n try {\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: this.getLegacyStateKey(stackName),\n })\n );\n\n if (!response.Body || !response.ETag) {\n return null;\n }\n\n const bodyString = await response.Body.transformToString();\n const state = this.parseStateBody(bodyString, stackName);\n\n // Region gate: the same `stackName` may have lived in a different region\n // before the user changed `env.region`. We do NOT want to silently load\n // that record for a different target region — that's the silent-failure\n // bug PR 1 fixes.\n if (state.region && state.region !== region) {\n this.logger.debug(\n `Legacy state for stack '${stackName}' has region '${state.region}', ` +\n `not '${region}' — skipping legacy fallback.`\n );\n return null;\n }\n\n return { state, etag: response.ETag };\n } catch (error) {\n if (isNoSuchKey(error)) return null;\n throw new StateError(\n `Failed to get legacy state for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Parse a state body and validate the schema version. Future-proofs against\n * a binary that predates schema version `N` reading a `version: N+1` blob:\n * the old binary would otherwise treat unknown fields as defaults and\n * silently lose data on the next save.\n */\n private parseStateBody(bodyString: string, stackName: string): StackState {\n let parsed: StackState;\n try {\n parsed = JSON.parse(bodyString) as StackState;\n } catch (error) {\n throw new StateError(\n `State file for stack '${stackName}' is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n\n const v = parsed.version;\n if (v !== undefined && !STATE_SCHEMA_VERSIONS_READABLE.includes(v)) {\n throw new StateError(\n `Unsupported state schema version ${String(v)} for stack '${stackName}'. ` +\n `This cdkd binary supports versions ${STATE_SCHEMA_VERSIONS_READABLE.join(', ')}. ` +\n `Upgrade cdkd to a version that supports schema ${String(v)}.`\n );\n }\n\n return parsed;\n }\n}\n\n/**\n * Treat S3 NoSuchKey-equivalents uniformly. The SDK throws `NoSuchKey` from\n * `GetObject` and `{name: 'NoSuchKey'}` from low-level callsites; HeadObject\n * raises `{name: 'NotFound'}` instead.\n */\nfunction isNoSuchKey(error: unknown): boolean {\n if (error instanceof NoSuchKey) return true;\n const name = (error as { name?: string } | null)?.name;\n return name === 'NoSuchKey';\n}\n","import {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n NoSuchKey,\n S3ServiceException,\n} from '@aws-sdk/client-s3';\nimport type { LockInfo } from '../types/state.js';\nimport type { StateBackendConfig } from '../types/config.js';\nimport { getLogger } from '../utils/logger.js';\nimport { LockError } from '../utils/error-handler.js';\nimport { hostname } from 'os';\n\n/**\n * Options for LockManager constructor\n */\nexport interface LockManagerOptions {\n /** Lock TTL in minutes (default: 30) */\n ttlMinutes?: number;\n}\n\n/**\n * S3-based lock manager using conditional writes (If-None-Match)\n *\n * Implements distributed locking using S3's If-None-Match: \"*\" condition\n * which ensures atomic lock acquisition.\n *\n * Locks have a TTL (time-to-live). Expired locks are automatically cleaned up\n * during acquisition attempts.\n */\nexport class LockManager {\n private logger = getLogger().child('LockManager');\n private s3Client: S3Client;\n private config: StateBackendConfig;\n private readonly ttlMs: number;\n\n constructor(s3Client: S3Client, config: StateBackendConfig, options?: LockManagerOptions) {\n this.s3Client = s3Client;\n this.config = config;\n const ttlMinutes = options?.ttlMinutes ?? 30;\n this.ttlMs = ttlMinutes * 60 * 1000;\n }\n\n /**\n * Get the S3 key for a stack's lock file.\n *\n * Locks are region-scoped, mirroring the state key layout\n * (`{prefix}/{stackName}/{region}/lock.json`). Two regions of the same\n * stackName can therefore be operated on in parallel without contention,\n * matching cdkd's parallel execution model.\n *\n * The `region` argument is required for new callers; for backwards\n * compatibility with `state list --long` (which only sees stack names),\n * passing `undefined` falls back to the legacy `{prefix}/{stackName}/lock.json`\n * key — that mode is purely for legacy lock cleanup and is NOT used by\n * deploy / destroy / diff anymore.\n */\n private getLockKey(stackName: string, region: string | undefined): string {\n if (region === undefined) {\n return `${this.config.prefix}/${stackName}/lock.json`;\n }\n return `${this.config.prefix}/${stackName}/${region}/lock.json`;\n }\n\n /**\n * Get default lock owner identifier\n */\n private getDefaultOwner(): string {\n try {\n const host = hostname();\n const user = process.env['USER'] || process.env['USERNAME'] || 'unknown';\n const pid = process.pid;\n return `${user}@${host}:${pid}`;\n } catch {\n return `cdkd:${process.pid}`;\n }\n }\n\n /**\n * Check if a lock is expired based on its expiresAt field\n */\n private isLockExpired(lockInfo: LockInfo): boolean {\n return Date.now() >= lockInfo.expiresAt;\n }\n\n /**\n * Format a human-readable duration from milliseconds\n */\n private formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes}m${remainingSeconds}s`;\n }\n\n /**\n * Try to acquire a lock for a stack\n *\n * Uses If-None-Match: \"*\" to ensure atomic lock acquisition.\n * If an expired lock exists, it will be cleaned up and re-acquired.\n *\n * @param stackName Stack name\n * @param region Target region (lock key is region-scoped)\n * @param owner Lock owner identifier (defaults to user@hostname:pid)\n * @param operation Operation being performed (e.g., \"deploy\", \"destroy\")\n */\n async acquireLock(\n stackName: string,\n region: string,\n owner?: string,\n operation?: string\n ): Promise<boolean> {\n const key = this.getLockKey(stackName, region);\n const lockOwner = owner || this.getDefaultOwner();\n const now = Date.now();\n\n const lockInfo: LockInfo = {\n owner: lockOwner,\n timestamp: now,\n expiresAt: now + this.ttlMs,\n ...(operation && { operation }),\n };\n\n try {\n this.logger.debug(`Attempting to acquire lock for stack: ${stackName} (${region})`);\n\n const lockBody = JSON.stringify(lockInfo, null, 2);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n Body: lockBody,\n ContentLength: Buffer.byteLength(lockBody),\n ContentType: 'application/json',\n IfNoneMatch: '*', // Only succeed if object doesn't exist\n })\n );\n\n this.logger.debug(`Lock acquired for stack: ${stackName} (${region}), owner: ${lockOwner}`);\n return true;\n } catch (error) {\n // Check for PreconditionFailed error (S3 condition not met - lock already exists)\n if (error instanceof S3ServiceException && error.name === 'PreconditionFailed') {\n this.logger.debug(`Lock already exists for stack: ${stackName} (${region})`);\n\n // Check if the existing lock is expired\n const existingLock = await this.getLockInfo(stackName, region);\n if (existingLock && this.isLockExpired(existingLock)) {\n this.logger.info(\n `Expired lock detected for stack: ${stackName} (${region}, owner: ${existingLock.owner}, ` +\n `expired ${this.formatDuration(now - existingLock.expiresAt)} ago). Cleaning up...`\n );\n\n // Delete the expired lock and retry acquisition\n await this.deleteLock(stackName, region);\n\n // Retry once after cleaning up expired lock\n try {\n const retryBody = JSON.stringify(lockInfo, null, 2);\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n Body: retryBody,\n ContentLength: Buffer.byteLength(retryBody),\n ContentType: 'application/json',\n IfNoneMatch: '*',\n })\n );\n\n this.logger.debug(\n `Lock acquired for stack: ${stackName} (${region}) after expired lock cleanup, owner: ${lockOwner}`\n );\n return true;\n } catch (retryError) {\n if (\n retryError instanceof S3ServiceException &&\n retryError.name === 'PreconditionFailed'\n ) {\n // Another process acquired the lock between our delete and retry\n this.logger.debug(\n `Lock was acquired by another process during expired lock cleanup for stack: ${stackName} (${region})`\n );\n return false;\n }\n throw retryError;\n }\n }\n\n return false;\n }\n\n throw new LockError(\n `Failed to acquire lock for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Get current lock information.\n *\n * `region` is required for the new region-scoped lock layout. Pass\n * `undefined` only to inspect a legacy `{prefix}/{stackName}/lock.json`\n * file (e.g. for state-listing tools that don't yet know the region).\n */\n async getLockInfo(stackName: string, region: string | undefined): Promise<LockInfo | null> {\n const key = this.getLockKey(stackName, region);\n\n try {\n this.logger.debug(`Getting lock info for stack: ${stackName}`);\n\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n\n if (!response.Body) {\n throw new LockError(`Lock file for stack '${stackName}' has no body`);\n }\n\n const bodyString = await response.Body.transformToString();\n const lockInfo = JSON.parse(bodyString) as LockInfo;\n\n this.logger.debug(`Lock info for stack: ${stackName}:`, lockInfo);\n\n return lockInfo;\n } catch (error) {\n if (error instanceof NoSuchKey) {\n this.logger.debug(`No lock exists for stack: ${stackName}`);\n return null;\n }\n\n if (error instanceof LockError) {\n throw error;\n }\n\n throw new LockError(\n `Failed to get lock info for stack '${stackName}': ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Check whether a lock currently exists for a stack\n *\n * Returns true if a lock file is present in S3 (regardless of expiry).\n * This is intended for read-only inspection (e.g. `cdkd state list --long`),\n * not for acquisition decisions — use `acquireLock` for that, which has its\n * own expired-lock cleanup logic.\n */\n async isLocked(stackName: string, region: string | undefined): Promise<boolean> {\n const lockInfo = await this.getLockInfo(stackName, region);\n return lockInfo !== null;\n }\n\n /**\n * Release a lock for a stack\n */\n async releaseLock(stackName: string, region: string): Promise<void> {\n const key = this.getLockKey(stackName, region);\n\n try {\n this.logger.debug(`Releasing lock for stack: ${stackName} (${region})`);\n\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n\n this.logger.debug(`Lock released for stack: ${stackName} (${region})`);\n } catch (error) {\n throw new LockError(\n `Failed to release lock for stack '${stackName}' (${region}): ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n }\n\n /**\n * Force release a lock regardless of owner or expiry status\n *\n * This is intended for CLI usage (e.g., --force-unlock flag) when a lock\n * is stuck and needs manual intervention.\n *\n * Pass `region: undefined` to operate on a legacy\n * `{prefix}/{stackName}/lock.json` file.\n */\n async forceReleaseLock(stackName: string, region: string | undefined): Promise<void> {\n const lockInfo = await this.getLockInfo(stackName, region);\n\n if (!lockInfo) {\n this.logger.warn(\n `No lock to force release for stack: ${stackName}${region ? ` (${region})` : ''}`\n );\n return;\n }\n\n this.logger.warn(\n `Force releasing lock for stack: ${stackName}${region ? ` (${region})` : ''}, ` +\n `owner: ${lockInfo.owner}` +\n `${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ''}` +\n `, expired: ${this.isLockExpired(lockInfo)}`\n );\n\n await this.deleteLock(stackName, region);\n }\n\n /**\n * Internal method to delete the lock file from S3\n */\n private async deleteLock(stackName: string, region: string | undefined): Promise<void> {\n const key = this.getLockKey(stackName, region);\n\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.config.bucket,\n Key: key,\n })\n );\n }\n\n /**\n * Acquire lock with retry logic\n *\n * Retries up to maxRetries times with retryDelay between attempts.\n * If lock is expired, cleans it up automatically.\n * On failure, provides helpful message with lock owner and expiry information.\n *\n * @param stackName Stack name\n * @param owner Lock owner identifier\n * @param operation Operation being performed\n * @param maxRetries Maximum number of retries (default: 3)\n * @param retryDelay Delay between retries in milliseconds (default: 2000)\n */\n async acquireLockWithRetry(\n stackName: string,\n region: string,\n owner?: string,\n operation?: string,\n maxRetries = 3,\n retryDelay = 2000\n ): Promise<void> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const acquired = await this.acquireLock(stackName, region, owner, operation);\n\n if (acquired) {\n return;\n }\n\n // Lock exists and is not expired - show info and possibly retry\n const lockInfo = await this.getLockInfo(stackName, region);\n\n if (lockInfo) {\n const remainingMs = lockInfo.expiresAt - Date.now();\n\n if (attempt < maxRetries) {\n this.logger.info(\n `Stack '${stackName}' (${region}) is locked by ${lockInfo.owner}` +\n `${lockInfo.operation ? ` (operation: ${lockInfo.operation})` : ''}` +\n `. Lock expires in ${this.formatDuration(remainingMs)}.` +\n ` Retrying in ${this.formatDuration(retryDelay)}... (attempt ${attempt + 1}/${maxRetries})`\n );\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n continue;\n }\n }\n }\n\n // Failed to acquire lock after all retries\n const lockInfo = await this.getLockInfo(stackName, region);\n const expiresIn = lockInfo ? this.formatDuration(lockInfo.expiresAt - Date.now()) : 'unknown';\n\n throw new LockError(\n `Failed to acquire lock for stack '${stackName}' (${region}) after ${maxRetries + 1} attempts. ` +\n (lockInfo\n ? `Locked by: ${lockInfo.owner}` +\n `${lockInfo.operation ? `, operation: ${lockInfo.operation}` : ''}` +\n `, expires in: ${expiresIn}. ` +\n `Use --force-unlock to manually release the lock.`\n : 'Lock exists but could not read lock info.')\n );\n }\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * CloudFormation template parser\n *\n * Provides utilities for parsing and extracting information from\n * CloudFormation templates\n */\nexport class TemplateParser {\n private logger = getLogger().child('TemplateParser');\n\n /**\n * Extract all resource logical IDs from template\n */\n getResourceIds(template: CloudFormationTemplate): string[] {\n return Object.keys(template.Resources);\n }\n\n /**\n * Get a specific resource from template\n */\n getResource(template: CloudFormationTemplate, logicalId: string): TemplateResource | undefined {\n return template.Resources[logicalId];\n }\n\n /**\n * Extract all dependencies for a resource\n *\n * Analyzes:\n * - DependsOn attribute\n * - Ref intrinsic functions\n * - Fn::GetAtt intrinsic functions\n */\n extractDependencies(resource: TemplateResource): Set<string> {\n const dependencies = new Set<string>();\n\n // 1. DependsOn attribute\n if (resource.DependsOn) {\n const dependsOn = Array.isArray(resource.DependsOn)\n ? resource.DependsOn\n : [resource.DependsOn];\n\n dependsOn.forEach((dep) => {\n if (typeof dep === 'string') {\n dependencies.add(dep);\n }\n });\n }\n\n // 2. Ref and Fn::GetAtt in Properties\n if (resource.Properties) {\n this.extractRefsFromValue(resource.Properties, dependencies);\n }\n\n // 3. Ref and Fn::GetAtt in other attributes (Metadata, UpdatePolicy, etc.)\n if (resource.Metadata) {\n this.extractRefsFromValue(resource.Metadata, dependencies);\n }\n\n return dependencies;\n }\n\n /**\n * Recursively extract Ref and Fn::GetAtt from a value\n */\n private extractRefsFromValue(value: unknown, dependencies: Set<string>): void {\n if (value === null || value === undefined) {\n return;\n }\n\n // Check if value is an object\n if (typeof value !== 'object') {\n return;\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n value.forEach((item) => this.extractRefsFromValue(item, dependencies));\n return;\n }\n\n // Handle objects\n const obj = value as Record<string, unknown>;\n\n // Check for Ref\n if ('Ref' in obj && typeof obj['Ref'] === 'string') {\n // Ignore pseudo parameters (AWS::Region, AWS::AccountId, etc.)\n if (!obj['Ref'].startsWith('AWS::')) {\n dependencies.add(obj['Ref']);\n }\n return;\n }\n\n // Check for Fn::GetAtt\n if ('Fn::GetAtt' in obj) {\n const getAtt = obj['Fn::GetAtt'];\n if (Array.isArray(getAtt) && getAtt.length >= 1 && typeof getAtt[0] === 'string') {\n dependencies.add(getAtt[0]);\n }\n return;\n }\n\n // Check for Fn::Sub\n // 1-arg form: \"Fn::Sub\": \"string with ${X} or ${X.Attr}\"\n // 2-arg form: \"Fn::Sub\": [\"string with ${X}\", { X: <value> }]\n // Per the CloudFormation spec, when ${X} appears in the body and X is NOT\n // in the explicit variable map (2-arg form), X resolves to Ref X — which\n // can point at a same-stack resource. The DAG must treat that as a real\n // dependency edge so the referenced resource is created first; otherwise\n // the resolver races and falls back to the literal placeholder, which AWS\n // rejects (see #275).\n if ('Fn::Sub' in obj) {\n const subValue = obj['Fn::Sub'];\n let body: string | undefined;\n let mapKeys: Set<string> | undefined;\n if (typeof subValue === 'string') {\n body = subValue;\n } else if (\n Array.isArray(subValue) &&\n subValue.length >= 1 &&\n typeof subValue[0] === 'string'\n ) {\n body = subValue[0];\n const variables: unknown = subValue[1];\n if (variables && typeof variables === 'object' && !Array.isArray(variables)) {\n const varMap = variables as Record<string, unknown>;\n mapKeys = new Set(Object.keys(varMap));\n // Recurse into the variable-map values — they may contain Ref / GetAtt\n // intrinsics that produce their own dependencies.\n Object.values(varMap).forEach((v) => this.extractRefsFromValue(v, dependencies));\n }\n }\n if (body !== undefined) {\n for (const match of body.matchAll(/\\$\\{([^}]+)\\}/g)) {\n const placeholder = match[1];\n if (!placeholder) continue;\n // ${X.AttrName} is an implicit Fn::GetAtt — depend on X (the prefix).\n // ${X} is an implicit Ref to X.\n const dot = placeholder.indexOf('.');\n const name = dot >= 0 ? placeholder.slice(0, dot) : placeholder;\n if (!name) continue;\n // Skip pseudo parameters (AWS::Region, AWS::AccountId, etc.).\n if (name.startsWith('AWS::')) continue;\n // Skip names provided by the 2-arg variable map.\n if (mapKeys?.has(name)) continue;\n dependencies.add(name);\n }\n }\n return;\n }\n\n // Check for Fn::Join / Fn::Select / Fn::Split\n // Fn::Join: [<delimiter: string>, [<item1>, <item2>, ...]]\n // Fn::Select: [<index: number-or-Ref>, <array-or-intrinsic>]\n // Fn::Split: [<delimiter: string>, <source-string-or-intrinsic>]\n // CDK emits these (especially Fn::Join) to construct ARNs that embed\n // sibling-resource Refs / Fn::GetAtt inside the array arguments — e.g.\n // DynamoDB `Table.tableArn` synthesizes as\n // Fn::Join: [':', ['arn', 'aws', 'dynamodb', {Ref:'AWS::Region'},\n // {Ref:'AWS::AccountId'}, {Fn::Join:['/',['table',{Ref:'MyTable'}]]}]]\n // and Fn::Select+Fn::Split is the canonical \"extract substring from ARN\"\n // pattern (Fn::Select: [5, Fn::Split: [':', Fn::GetAtt: [..., 'Arn']]]).\n // The buried Ref / Fn::GetAtt MUST contribute a DAG edge so the consumer\n // is ordered after its dependency; otherwise the deploy races. Explicit\n // recursion through each intrinsic's array argument keeps that support\n // load-bearing instead of relying on the generic fall-through below to\n // accidentally find them (see issue #286 — same class as #275/#276).\n // We do NOT add edges for the intrinsic wrapper itself, only for inner\n // refs the recursion uncovers (Ref / Fn::GetAtt / Fn::Sub, plus any\n // further-nested Fn::Join / Fn::Select / Fn::Split chain).\n if ('Fn::Join' in obj) {\n const joinValue = obj['Fn::Join'];\n if (Array.isArray(joinValue) && joinValue.length >= 2) {\n // Skip the delimiter (joinValue[0]); recurse into the items array.\n this.extractRefsFromValue(joinValue[1], dependencies);\n }\n return;\n }\n if ('Fn::Select' in obj) {\n const selectValue = obj['Fn::Select'];\n if (Array.isArray(selectValue) && selectValue.length >= 2) {\n // Index may itself be an intrinsic (rare but valid); recurse into both.\n this.extractRefsFromValue(selectValue[0], dependencies);\n this.extractRefsFromValue(selectValue[1], dependencies);\n }\n return;\n }\n if ('Fn::Split' in obj) {\n const splitValue = obj['Fn::Split'];\n if (Array.isArray(splitValue) && splitValue.length >= 2) {\n // Skip the delimiter (splitValue[0]); recurse into the source value.\n this.extractRefsFromValue(splitValue[1], dependencies);\n }\n return;\n }\n\n // Recursively process all values\n Object.values(obj).forEach((v) => this.extractRefsFromValue(v, dependencies));\n }\n\n /**\n * Check if a resource has a specific property\n */\n hasProperty(resource: TemplateResource, propertyPath: string): boolean {\n if (!resource.Properties) {\n return false;\n }\n\n const parts = propertyPath.split('.');\n let current: unknown = resource.Properties;\n\n for (const part of parts) {\n if (typeof current !== 'object' || current === null) {\n return false;\n }\n\n const obj = current as Record<string, unknown>;\n if (!(part in obj)) {\n return false;\n }\n\n current = obj[part];\n }\n\n return true;\n }\n\n /**\n * Get a property value from a resource\n */\n getProperty(resource: TemplateResource, propertyPath: string): unknown {\n if (!resource.Properties) {\n return undefined;\n }\n\n const parts = propertyPath.split('.');\n let current: unknown = resource.Properties;\n\n for (const part of parts) {\n if (typeof current !== 'object' || current === null) {\n return undefined;\n }\n\n const obj = current as Record<string, unknown>;\n if (!(part in obj)) {\n return undefined;\n }\n\n current = obj[part];\n }\n\n return current;\n }\n\n /**\n * Validate template structure\n */\n validateTemplate(template: unknown): template is CloudFormationTemplate {\n if (typeof template !== 'object' || template === null) {\n this.logger.error('Template is not an object');\n return false;\n }\n\n const t = template as Record<string, unknown>;\n\n if (!('Resources' in t)) {\n this.logger.error('Template missing Resources section');\n return false;\n }\n\n if (typeof t['Resources'] !== 'object' || t['Resources'] === null) {\n this.logger.error('Template Resources is not an object');\n return false;\n }\n\n const resources = t['Resources'] as Record<string, unknown>;\n\n // Validate each resource has a Type\n for (const [logicalId, resource] of Object.entries(resources)) {\n if (typeof resource !== 'object' || resource === null) {\n this.logger.error(`Resource ${logicalId} is not an object`);\n return false;\n }\n\n const r = resource as Record<string, unknown>;\n if (!('Type' in r) || typeof r['Type'] !== 'string') {\n this.logger.error(`Resource ${logicalId} missing Type or Type is not a string`);\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Get all resources of a specific type\n */\n getResourcesByType(\n template: CloudFormationTemplate,\n resourceType: string\n ): Map<string, TemplateResource> {\n const resources = new Map<string, TemplateResource>();\n\n for (const [logicalId, resource] of Object.entries(template.Resources)) {\n if (resource.Type === resourceType) {\n resources.set(logicalId, resource);\n }\n }\n\n return resources;\n }\n\n /**\n * Count resources in template\n */\n countResources(template: CloudFormationTemplate): number {\n return Object.keys(template.Resources).length;\n }\n}\n","/**\n * Lambda VpcConfig implicit deletion dependencies.\n *\n * AWS::Lambda::Function with a VpcConfig holds onto an ENI in the configured\n * subnets / security groups for some time AFTER the function is deleted.\n * If we tear down the VPC's Subnets / SecurityGroups before the ENI is fully\n * detached, the EC2 API rejects the delete with \"has dependencies\" /\n * \"DependencyViolation\".\n *\n * The Ref-based dependency expressed by `VpcConfig.SubnetIds: [{ Ref: ... }]`\n * is normally captured by `TemplateParser.extractDependencies` and recorded\n * in `state.dependencies`, which already gives the correct teardown order.\n * This module provides a defense-in-depth, property-based extractor so the\n * ordering still holds when:\n * - state was written by an older cdkd version that did not record the dep\n * - extractDependencies misses a wrapping intrinsic for some reason\n *\n * The returned edges express: \"the Lambda must be deleted BEFORE each\n * referenced Subnet / SecurityGroup\".\n */\nimport type { TemplateResource } from '../types/resource.js';\n\n/** A single dependency edge for the DELETE phase. */\nexport interface DeleteDepEdge {\n /** Logical ID that must be deleted FIRST. */\n before: string;\n /** Logical ID that must be deleted AFTER `before`. */\n after: string;\n}\n\n/**\n * Minimal shape used by extractLambdaVpcDeleteDeps: a logical-ID-keyed map of\n * resources where each entry exposes a CloudFormation-style `Type` and\n * `Properties`. Both `TemplateResource` and the ad-hoc per-stack template\n * built in destroy.ts conform to this.\n */\nexport type ResourceLike = Pick<TemplateResource, 'Type' | 'Properties'>;\n\n/**\n * Extract implicit delete edges for AWS::Lambda::Function with a VpcConfig.\n *\n * For each Lambda function in the input map, scans\n * `Properties.VpcConfig.SubnetIds` and `Properties.VpcConfig.SecurityGroupIds`\n * for `{ Ref: <logicalId> }` / `{ \"Fn::GetAtt\": [<logicalId>, ...] }`\n * references. Every referenced ID that exists in the input map produces an\n * edge `{ before: <lambdaId>, after: <targetId> }`.\n *\n * Notes:\n * - Properties already resolved to physical IDs (state.properties after\n * deploy) yield no edges. That is intentional — in that case the caller\n * should rely on `state.dependencies`, which preserves logical IDs.\n * - Self-edges and edges pointing to absent IDs are filtered out.\n * - Returned edges are de-duplicated.\n */\nexport function extractLambdaVpcDeleteDeps(\n resources: Record<string, ResourceLike>\n): DeleteDepEdge[] {\n const edges: DeleteDepEdge[] = [];\n const seen = new Set<string>();\n\n for (const [lambdaId, resource] of Object.entries(resources)) {\n if (resource.Type !== 'AWS::Lambda::Function') continue;\n\n const vpcConfig = (resource.Properties ?? {})['VpcConfig'];\n if (!isObject(vpcConfig)) continue;\n\n const targets = new Set<string>();\n collectRefIds(vpcConfig['SubnetIds'], targets);\n collectRefIds(vpcConfig['SecurityGroupIds'], targets);\n\n for (const targetId of targets) {\n if (targetId === lambdaId) continue;\n if (!(targetId in resources)) continue;\n const key = `${lambdaId}\\u0000${targetId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n edges.push({ before: lambdaId, after: targetId });\n }\n }\n\n return edges;\n}\n\nfunction isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n/**\n * Walk `value` (typically an array) and collect every logical ID referenced\n * via `{ Ref: ... }` or `{ \"Fn::GetAtt\": [<id>, ...] }`. Pseudo parameters\n * (Refs starting with `AWS::`) are skipped.\n */\nfunction collectRefIds(value: unknown, out: Set<string>): void {\n if (value === null || value === undefined) return;\n\n if (Array.isArray(value)) {\n for (const item of value) collectRefIds(item, out);\n return;\n }\n\n if (!isObject(value)) return;\n\n if (typeof value['Ref'] === 'string') {\n const ref = value['Ref'];\n if (!ref.startsWith('AWS::')) out.add(ref);\n return;\n }\n\n if (Array.isArray(value['Fn::GetAtt'])) {\n const arr = value['Fn::GetAtt'];\n if (typeof arr[0] === 'string') out.add(arr[0]);\n return;\n }\n\n // Other intrinsics (Fn::Join, Fn::If, ...) cannot be statically resolved\n // without a full IntrinsicResolver pass; the regular extractDependencies\n // path handles those at deploy time.\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\n\n/**\n * CDK-injected defensive `DependsOn` edges that block deploy parallelization\n * but are not required for AWS API correctness.\n *\n * The CDK constructs eagerly inject `DependsOn` from VPC Lambdas (and adjacent\n * resources — IAM Role / Policy that the Lambda uses, the Lambda::Url that\n * derives its FunctionUrl from the Lambda, the EventSourceMapping that wires\n * the Lambda to a queue) onto the private subnets' `DefaultRoute` /\n * `RouteTableAssociation` so that nothing tries to invoke the Lambda before\n * its egress path to the internet is up. The dependency is real at *runtime*\n * (a Lambda code call to a third-party API can't reach the internet without a\n * NAT route), but it is NOT required at *deploy time* — `CreateFunction` /\n * `CreateFunctionUrlConfig` / `AddPermission` / `CreateEventSourceMapping`\n * all accept a function in `Pending` state and AWS resolves the asynchronous\n * ENI provisioning + route binding in the background. cdkd's existing Custom\n * Resource path already relies on this: the post-`CreateFunction` `State=Active`\n * wait was deliberately moved to `CustomResourceProvider.sendRequest` (the\n * one consumer that breaks against `Pending`) so that VPC Lambdas don't\n * double the deploy time of the average benchmark stack — see\n * `src/provisioning/providers/lambda-function-provider.ts` and PR #121.\n *\n * The cost of leaving this defensive edge in place: a CloudFront Distribution\n * whose Origin is `Lambda::Url.FunctionUrl` cannot start its ~3-min edge\n * propagation until the Lambda finishes, which itself cannot start until the\n * NAT GW is `available` (~2 min). That serialization adds ~5 min to every\n * VPC + Lambda + CloudFront stack. Relaxing the defensive edge collapses\n * the two waits onto one timeline (`max(NAT, CF) ≈ CF`), measured at −45.6%\n * on `bench-cdk-sample` (387s → 211s).\n *\n * The list below is intentionally narrow (`from`-types that the CDK actually\n * decorates with these route DependsOns + `to`-types that are pure egress\n * wiring). It is NOT a general \"ignore all DependsOn\" toggle — Ref / GetAtt\n * edges are untouched, and DependsOn pairs outside this list are also kept.\n */\nconst DEFENSIVE_DEPENDS_ON_TYPE_PAIRS: ReadonlyArray<{\n fromType: string;\n toType: string;\n}> = [\n // VPC Lambda's execution Role (and its inline Policy) get DependsOn'd onto\n // the route only because CDK assumes the Lambda will run before the route\n // is up. The Role/Policy create call itself is VPC-agnostic.\n { fromType: 'AWS::IAM::Role', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::IAM::Role', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n { fromType: 'AWS::IAM::Policy', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::IAM::Policy', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n\n // VPC Lambda itself: CreateFunction returns synchronously while the\n // function is still in Pending; the route only matters once the function\n // is invoked at runtime.\n { fromType: 'AWS::Lambda::Function', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::Lambda::Function', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n\n // Lambda::Url is just a deterministic URL derivation off the function; it\n // doesn't need the function's runtime egress to exist.\n { fromType: 'AWS::Lambda::Url', toType: 'AWS::EC2::Route' },\n { fromType: 'AWS::Lambda::Url', toType: 'AWS::EC2::SubnetRouteTableAssociation' },\n\n // EventSourceMapping just registers the wire-up; AWS handles delivery\n // async and will retry once the function reaches Active.\n { fromType: 'AWS::Lambda::EventSourceMapping', toType: 'AWS::EC2::Route' },\n {\n fromType: 'AWS::Lambda::EventSourceMapping',\n toType: 'AWS::EC2::SubnetRouteTableAssociation',\n },\n];\n\n/**\n * Compute the set of DependsOn entries on `resource` that fall under one of\n * the CDK-defensive type pairs above. The DAG builder skips these edges\n * when relaxation is enabled.\n *\n * Returns the subset of DependsOn target logical IDs that can be skipped.\n * DependsOn entries that don't match any rule (or that aren't strings, or\n * that point to non-existent resources) are returned untouched (i.e. NOT in\n * the skip set), so they continue to be added to the graph.\n */\nexport function defensiveDependsOnToSkip(\n resource: TemplateResource,\n template: CloudFormationTemplate\n): Set<string> {\n const skip = new Set<string>();\n\n if (!resource.DependsOn) {\n return skip;\n }\n\n const dependsOn = Array.isArray(resource.DependsOn) ? resource.DependsOn : [resource.DependsOn];\n\n for (const dep of dependsOn) {\n if (typeof dep !== 'string') continue;\n const target = template.Resources[dep];\n if (!target) continue;\n const fromType = resource.Type;\n const toType = target.Type;\n if (!fromType || !toType) continue;\n const matched = DEFENSIVE_DEPENDS_ON_TYPE_PAIRS.some(\n (pair) => pair.fromType === fromType && pair.toType === toType\n );\n if (matched) {\n skip.add(dep);\n }\n }\n\n return skip;\n}\n","import graphlib from 'graphlib';\nimport type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport { TemplateParser } from './template-parser.js';\nimport { extractLambdaVpcDeleteDeps } from './lambda-vpc-deps.js';\nimport { defensiveDependsOnToSkip } from './cdk-defensive-deps.js';\nimport { getLogger } from '../utils/logger.js';\nimport { DependencyError } from '../utils/error-handler.js';\n\nconst { Graph, alg } = graphlib;\ntype GraphType = graphlib.Graph;\n\nconst IAM_ROLE_POLICY_TYPES: ReadonlySet<string> = new Set([\n 'AWS::IAM::Policy',\n 'AWS::IAM::RolePolicy',\n 'AWS::IAM::ManagedPolicy',\n]);\n\nexport interface DagBuilderOptions {\n /**\n * When true, drop the CDK-injected defensive DependsOn edges that block\n * VPC-Lambda deploys behind NAT route stabilization. Off by default — see\n * `cdk-defensive-deps.ts` for the rationale and the type-pair allowlist.\n */\n relaxCdkVpcDefensiveDeps?: boolean;\n}\n\n/**\n * Dependency graph builder for CloudFormation resources\n *\n * Builds a directed acyclic graph (DAG) of resource dependencies\n * based on Ref, Fn::GetAtt, and DependsOn\n */\nexport class DagBuilder {\n private logger = getLogger().child('DagBuilder');\n private parser = new TemplateParser();\n private options: DagBuilderOptions;\n\n constructor(options: DagBuilderOptions = {}) {\n this.options = options;\n }\n\n /**\n * Build dependency graph from CloudFormation template\n *\n * Creates a directed graph where:\n * - Nodes = resource logical IDs\n * - Edges = dependencies (A -> B means B depends on A)\n */\n buildGraph(template: CloudFormationTemplate): GraphType {\n const graph = new Graph({ directed: true });\n\n this.logger.debug('Building dependency graph...');\n\n // Add all resources as nodes\n const resourceIds = this.parser.getResourceIds(template);\n resourceIds.forEach((logicalId) => {\n const resource = this.parser.getResource(template, logicalId);\n graph.setNode(logicalId, resource);\n this.logger.debug(`Added node: ${logicalId} (${resource?.Type})`);\n });\n\n this.logger.debug(`Total nodes: ${resourceIds.length}`);\n\n // Add edges for dependencies\n let edgeCount = 0;\n let relaxedEdgeCount = 0;\n for (const logicalId of resourceIds) {\n const resource = this.parser.getResource(template, logicalId);\n if (!resource) {\n continue;\n }\n\n const dependencies = this.parser.extractDependencies(resource);\n // When relaxation is enabled, compute the subset of DependsOn entries\n // (NOT Ref / GetAtt — those are real data dependencies) that the CDK\n // injected defensively for runtime egress reasons. Skip them at edge\n // insertion time. See `cdk-defensive-deps.ts` for the type-pair list.\n const skip = this.options.relaxCdkVpcDefensiveDeps\n ? defensiveDependsOnToSkip(resource, template)\n : null;\n\n for (const depId of dependencies) {\n if (skip?.has(depId)) {\n relaxedEdgeCount++;\n this.logger.debug(\n `Skipped CDK-defensive DependsOn edge: ${depId} -> ${logicalId} (default; opt out with --no-aggressive-vpc-parallel)`\n );\n continue;\n }\n // Only add edge if the dependency exists in the template\n if (graph.hasNode(depId)) {\n graph.setEdge(depId, logicalId); // depId -> logicalId (logicalId depends on depId)\n edgeCount++;\n this.logger.debug(`Added edge: ${depId} -> ${logicalId}`);\n } else {\n this.logger.warn(\n `Resource ${logicalId} depends on ${depId}, but ${depId} not found in template`\n );\n }\n }\n }\n if (relaxedEdgeCount > 0) {\n this.logger.info(\n `[DagBuilder] Relaxed ${relaxedEdgeCount} CDK-defensive DependsOn edge(s) (default; opt out with --no-aggressive-vpc-parallel)`\n );\n }\n\n this.logger.debug(`Dependency graph built: ${resourceIds.length} nodes, ${edgeCount} edges`);\n\n // Add implicit edges from IAM::Policy (and friends) attached to a Custom\n // Resource's ServiceToken Lambda's execution role.\n // WHY: CloudFormation templates only express deps via Ref/GetAtt/DependsOn.\n // A Custom Resource typically refs only the Lambda (via ServiceToken), not the\n // inline IAM::Policy that grants the Lambda its runtime permissions. Without this\n // edge the Custom Resource can run before the policy attachment API returns, so\n // the handler hits AccessDenied in the middle of deploy.\n edgeCount += this.addCustomResourcePolicyEdges(graph, template);\n\n // Defense-in-depth edges for AWS::Lambda::Function VpcConfig: even though\n // Refs in `Properties.VpcConfig.SubnetIds` / `SecurityGroupIds` are\n // already picked up by extractDependencies (and so will produce edges in\n // the loop above), an explicit pass guards against future regressions in\n // the recursive extractor and makes the Lambda-vs-VPC ordering visible\n // in the DAG even when those properties are wrapped in unusual shapes.\n edgeCount += this.addLambdaVpcEdges(graph, template);\n\n // Validate graph is acyclic\n if (!alg.isAcyclic(graph)) {\n const cycles = this.findCycles(graph);\n throw new DependencyError(\n `Circular dependency detected in template. Cycles: ${cycles.map((c) => c.join(' -> ')).join('; ')}`\n );\n }\n\n return graph;\n }\n\n /**\n * Get execution levels via topological sort\n *\n * Returns resources grouped by execution level:\n * - Level 0: Resources with no dependencies\n * - Level 1: Resources that depend only on Level 0\n * - Level N: Resources that depend on Level 0..N-1\n *\n * Resources in the same level can be executed in parallel.\n */\n getExecutionLevels(graph: GraphType): string[][] {\n const levels: string[][] = [];\n const graphCopy = new Graph({ directed: true });\n\n // Copy the graph\n graph.nodes().forEach((node: string) => {\n graphCopy.setNode(node, graph.node(node));\n });\n graph.edges().forEach((edge: graphlib.Edge) => {\n graphCopy.setEdge(edge.v, edge.w);\n });\n\n this.logger.debug('Computing execution levels...');\n\n let levelNum = 0;\n while (graphCopy.nodeCount() > 0) {\n // Find nodes with no incoming edges (no dependencies)\n const readyNodes = graphCopy.nodes().filter((node) => {\n const predecessors = graphCopy.predecessors(node);\n return !predecessors || predecessors.length === 0;\n });\n\n if (readyNodes.length === 0) {\n // This should not happen if graph is acyclic, but check anyway\n const remaining = graphCopy.nodes();\n throw new DependencyError(\n `Circular dependency detected. Remaining nodes: ${remaining.join(', ')}`\n );\n }\n\n this.logger.debug(\n `Level ${levelNum}: ${readyNodes.length} resources - ${readyNodes.join(', ')}`\n );\n levels.push(readyNodes);\n\n // Remove these nodes from the graph\n readyNodes.forEach((node) => {\n graphCopy.removeNode(node);\n });\n\n levelNum++;\n }\n\n this.logger.debug(`Execution levels computed: ${levels.length} levels`);\n\n return levels;\n }\n\n /**\n * Find all cycles in the graph\n */\n private findCycles(graph: GraphType): string[][] {\n const cycles: string[][] = [];\n const visited = new Set<string>();\n const recursionStack = new Set<string>();\n const path: string[] = [];\n\n const dfs = (node: string): boolean => {\n visited.add(node);\n recursionStack.add(node);\n path.push(node);\n\n const successors = graph.successors(node) || [];\n\n for (const successor of successors) {\n if (!visited.has(successor)) {\n if (dfs(successor)) {\n return true;\n }\n } else if (recursionStack.has(successor)) {\n // Found a cycle\n const cycleStart = path.indexOf(successor);\n const cycle = path.slice(cycleStart);\n cycle.push(successor);\n cycles.push(cycle);\n return true;\n }\n }\n\n path.pop();\n recursionStack.delete(node);\n return false;\n };\n\n for (const node of graph.nodes()) {\n if (!visited.has(node)) {\n dfs(node);\n }\n }\n\n return cycles;\n }\n\n /**\n * Get all dependencies for a resource (transitive)\n */\n getAllDependencies(graph: GraphType, logicalId: string): Set<string> {\n const dependencies = new Set<string>();\n\n const visit = (node: string) => {\n const predecessors = graph.predecessors(node) || [];\n predecessors.forEach((pred: string) => {\n if (!dependencies.has(pred)) {\n dependencies.add(pred);\n visit(pred); // Recursively visit dependencies\n }\n });\n };\n\n visit(logicalId);\n return dependencies;\n }\n\n /**\n * Get all dependents for a resource (transitive)\n */\n getAllDependents(graph: GraphType, logicalId: string): Set<string> {\n const dependents = new Set<string>();\n\n const visit = (node: string) => {\n const successors = graph.successors(node) || [];\n successors.forEach((succ: string) => {\n if (!dependents.has(succ)) {\n dependents.add(succ);\n visit(succ); // Recursively visit dependents\n }\n });\n };\n\n visit(logicalId);\n return dependents;\n }\n\n /**\n * Get direct dependencies for a resource\n */\n getDirectDependencies(graph: GraphType, logicalId: string): string[] {\n return graph.predecessors(logicalId) || [];\n }\n\n /**\n * Get direct dependents for a resource\n */\n getDirectDependents(graph: GraphType, logicalId: string): string[] {\n return graph.successors(logicalId) || [];\n }\n\n /**\n * Check if resource A depends on resource B\n */\n dependsOn(graph: GraphType, resourceA: string, resourceB: string): boolean {\n const deps = this.getAllDependencies(graph, resourceA);\n return deps.has(resourceB);\n }\n\n /**\n * Add implicit edges from IAM::Policy resources to Custom Resources whose\n * ServiceToken Lambda's execution role those policies attach to.\n *\n * Returns the number of edges added.\n */\n private addCustomResourcePolicyEdges(graph: GraphType, template: CloudFormationTemplate): number {\n const rolePolicies = this.buildRolePoliciesMap(template);\n if (rolePolicies.size === 0) {\n return 0;\n }\n\n let added = 0;\n for (const logicalId of this.parser.getResourceIds(template)) {\n const resource = this.parser.getResource(template, logicalId);\n if (!resource || !this.isCustomResourceType(resource.Type)) {\n continue;\n }\n\n const serviceToken = (resource.Properties ?? {})['ServiceToken'];\n const lambdaId = this.extractLogicalIdFromReference(serviceToken);\n if (!lambdaId) continue;\n\n const lambdaResource = this.parser.getResource(template, lambdaId);\n if (!lambdaResource || lambdaResource.Type !== 'AWS::Lambda::Function') {\n continue;\n }\n\n const roleId = this.extractLogicalIdFromReference((lambdaResource.Properties ?? {})['Role']);\n if (!roleId) continue;\n\n const policies = rolePolicies.get(roleId);\n if (!policies) continue;\n\n for (const policyId of policies) {\n if (policyId === logicalId) continue;\n if (!graph.hasNode(policyId)) continue;\n if (graph.hasEdge(policyId, logicalId)) continue;\n graph.setEdge(policyId, logicalId);\n added++;\n this.logger.debug(\n `Added implicit edge (custom resource policy): ${policyId} -> ${logicalId}`\n );\n }\n }\n\n if (added > 0) {\n this.logger.debug(`Added ${added} implicit edges for custom resource policies`);\n }\n return added;\n }\n\n /**\n * Add edges from Subnets / SecurityGroups referenced by an\n * AWS::Lambda::Function VpcConfig to the Lambda itself.\n *\n * Same direction as a normal `Ref`-derived edge (Subnet -> Lambda), so for\n * deploy this just duplicates what extractDependencies already produced.\n * The point is robustness: if a future template massages the VpcConfig\n * shape in a way the recursive extractor doesn't anticipate, this pass\n * still ties the Lambda to its networking resources so that the\n * deletion-time reverse traversal continues to delete Lambda before\n * Subnet / SecurityGroup.\n *\n * Returns the number of NEW edges added (existing edges are skipped).\n */\n private addLambdaVpcEdges(graph: GraphType, template: CloudFormationTemplate): number {\n const edges = extractLambdaVpcDeleteDeps(template.Resources);\n if (edges.length === 0) return 0;\n\n let added = 0;\n for (const edge of edges) {\n // edge: { before: lambdaId, after: vpcResourceId }\n // Edge convention: setEdge(depId, dependentId) means dependentId\n // depends on depId. The Lambda depends on the Subnet / SG, so\n // depId = vpcResourceId (after), dependentId = lambdaId (before).\n const depId = edge.after;\n const dependentId = edge.before;\n if (!graph.hasNode(depId) || !graph.hasNode(dependentId)) continue;\n if (graph.hasEdge(depId, dependentId)) continue;\n graph.setEdge(depId, dependentId);\n added++;\n this.logger.debug(`Added implicit edge (lambda vpc): ${depId} -> ${dependentId}`);\n }\n\n if (added > 0) {\n this.logger.debug(`Added ${added} implicit edges for Lambda VpcConfig`);\n }\n return added;\n }\n\n private isCustomResourceType(type: string): boolean {\n return type === 'AWS::CloudFormation::CustomResource' || type.startsWith('Custom::');\n }\n\n /**\n * Build a map of roleLogicalId -> Set<policyLogicalId> by scanning the\n * template for IAM::Policy / IAM::RolePolicy / IAM::ManagedPolicy resources\n * that attach to a role by Ref/GetAtt.\n */\n private buildRolePoliciesMap(template: CloudFormationTemplate): Map<string, Set<string>> {\n const map = new Map<string, Set<string>>();\n\n for (const [policyId, resource] of Object.entries(template.Resources)) {\n if (!IAM_ROLE_POLICY_TYPES.has(resource.Type)) continue;\n\n for (const roleId of this.extractAttachedRoleIds(resource)) {\n let set = map.get(roleId);\n if (!set) {\n set = new Set();\n map.set(roleId, set);\n }\n set.add(policyId);\n }\n }\n\n return map;\n }\n\n /**\n * Extract the logical IDs of IAM::Role resources that a policy resource\n * attaches to. Supports both `Roles: [Ref]` (IAM::Policy / IAM::ManagedPolicy)\n * and `RoleName: Ref` (IAM::RolePolicy) shapes.\n */\n private extractAttachedRoleIds(resource: TemplateResource): string[] {\n const ids: string[] = [];\n const props = resource.Properties ?? {};\n\n const roles = props['Roles'];\n if (Array.isArray(roles)) {\n for (const entry of roles) {\n const id = this.extractLogicalIdFromReference(entry);\n if (id) ids.push(id);\n }\n }\n\n const roleName = props['RoleName'];\n const roleNameId = this.extractLogicalIdFromReference(roleName);\n if (roleNameId) ids.push(roleNameId);\n\n return ids;\n }\n\n /**\n * Extract a resource logical ID from a direct Ref or Fn::GetAtt expression.\n * Returns undefined for literals or intrinsics we can't statically resolve\n * (Fn::Join, Fn::ImportValue, etc.) — callers should skip in that case.\n */\n private extractLogicalIdFromReference(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const obj = value as Record<string, unknown>;\n\n if ('Ref' in obj && typeof obj['Ref'] === 'string') {\n const ref = obj['Ref'];\n return ref.startsWith('AWS::') ? undefined : ref;\n }\n\n if ('Fn::GetAtt' in obj) {\n const getAtt = obj['Fn::GetAtt'];\n if (Array.isArray(getAtt) && typeof getAtt[0] === 'string') {\n return getAtt[0];\n }\n }\n\n return undefined;\n }\n}\n","/**\n * Replacement rules for AWS resource types\n *\n * Defines which property changes require resource replacement (delete + recreate)\n * vs. in-place updates.\n *\n * Based on CloudFormation update behaviors:\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html\n */\n\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Resource replacement rule\n */\ninterface ReplacementRule {\n /** Properties that always require replacement when changed */\n replacementProperties: Set<string>;\n /** Properties that never require replacement */\n updateableProperties?: Set<string>;\n /** Custom logic for conditional replacement */\n conditionalReplacements?: Map<string, (oldValue: unknown, newValue: unknown) => boolean>;\n}\n\n/**\n * Replacement rules registry\n *\n * Maps resource types to their replacement rules\n */\nexport class ReplacementRulesRegistry {\n private logger = getLogger().child('ReplacementRulesRegistry');\n private rules = new Map<string, ReplacementRule>();\n\n constructor() {\n this.initializeRules();\n }\n\n /**\n * Check if a property change requires replacement\n */\n requiresReplacement(\n resourceType: string,\n propertyPath: string,\n oldValue: unknown,\n newValue: unknown\n ): boolean {\n const rule = this.rules.get(resourceType);\n\n if (!rule) {\n // No specific rule for this resource type\n // Conservative approach: assume replacement may be required\n this.logger.debug(\n `No replacement rule for ${resourceType}, conservatively assuming replacement may be required for ${propertyPath}`\n );\n return false; // Default to updateable for unknown types\n }\n\n // Check if property always requires replacement\n if (rule.replacementProperties.has(propertyPath)) {\n this.logger.debug(`Property ${propertyPath} of ${resourceType} requires replacement`);\n return true;\n }\n\n // Check if property is explicitly updateable\n if (rule.updateableProperties?.has(propertyPath)) {\n return false;\n }\n\n // Check conditional replacements\n if (rule.conditionalReplacements?.has(propertyPath)) {\n const condition = rule.conditionalReplacements.get(propertyPath);\n if (condition) {\n const requires = condition(oldValue, newValue);\n this.logger.debug(\n `Conditional replacement for ${propertyPath} of ${resourceType}: ${requires}`\n );\n return requires;\n }\n }\n\n // If not explicitly defined, assume it's updateable\n return false;\n }\n\n /**\n * Initialize replacement rules for common AWS resource types\n */\n private initializeRules(): void {\n // S3 Bucket\n this.rules.set('AWS::S3::Bucket', {\n replacementProperties: new Set([\n 'BucketName', // Changing bucket name requires replacement\n ]),\n updateableProperties: new Set([\n 'Tags',\n 'VersioningConfiguration',\n 'LifecycleConfiguration',\n 'PublicAccessBlockConfiguration',\n 'BucketEncryption',\n 'LoggingConfiguration',\n 'WebsiteConfiguration',\n 'CorsConfiguration',\n 'NotificationConfiguration',\n ]),\n });\n\n // Lambda Function\n this.rules.set('AWS::Lambda::Function', {\n replacementProperties: new Set([\n 'FunctionName', // Changing function name requires replacement\n ]),\n updateableProperties: new Set([\n 'Code',\n 'Handler',\n 'Runtime',\n 'Description',\n 'Timeout',\n 'MemorySize',\n 'Role',\n 'Environment',\n 'Tags',\n 'VpcConfig',\n 'DeadLetterConfig',\n 'TracingConfig',\n 'Layers',\n 'FileSystemConfigs',\n ]),\n });\n\n // DynamoDB Table\n this.rules.set('AWS::DynamoDB::Table', {\n replacementProperties: new Set([\n 'TableName', // Changing table name requires replacement\n 'KeySchema', // Changing key schema requires replacement\n 'AttributeDefinitions', // Changing attributes (in key) requires replacement\n ]),\n updateableProperties: new Set([\n 'BillingMode',\n 'ProvisionedThroughput',\n 'GlobalSecondaryIndexes',\n 'LocalSecondaryIndexes',\n 'StreamSpecification',\n 'SSESpecification',\n 'Tags',\n 'TimeToLiveSpecification',\n 'PointInTimeRecoverySpecification',\n ]),\n });\n\n // SQS Queue\n this.rules.set('AWS::SQS::Queue', {\n replacementProperties: new Set([\n 'QueueName', // Changing queue name requires replacement\n 'FifoQueue', // Changing FIFO attribute requires replacement\n 'ContentBasedDeduplication', // Only for FIFO queues\n ]),\n updateableProperties: new Set([\n 'DelaySeconds',\n 'MaximumMessageSize',\n 'MessageRetentionPeriod',\n 'ReceiveMessageWaitTimeSeconds',\n 'VisibilityTimeout',\n 'RedrivePolicy',\n 'Tags',\n ]),\n });\n\n // IAM Role\n this.rules.set('AWS::IAM::Role', {\n replacementProperties: new Set([\n 'RoleName', // Changing role name requires replacement\n ]),\n updateableProperties: new Set([\n 'AssumeRolePolicyDocument',\n 'Description',\n 'ManagedPolicyArns',\n 'MaxSessionDuration',\n 'Path',\n 'PermissionsBoundary',\n 'Policies',\n 'Tags',\n ]),\n });\n\n // SNS Topic\n this.rules.set('AWS::SNS::Topic', {\n replacementProperties: new Set([\n 'TopicName', // Changing topic name requires replacement\n ]),\n updateableProperties: new Set(['DisplayName', 'Subscription', 'KmsMasterKeyId', 'Tags']),\n });\n\n // ECR Repository\n this.rules.set('AWS::ECR::Repository', {\n replacementProperties: new Set([\n 'RepositoryName', // Changing repository name requires replacement\n ]),\n updateableProperties: new Set([\n 'ImageScanningConfiguration',\n 'ImageTagMutability',\n 'LifecyclePolicy',\n 'RepositoryPolicyText',\n 'Tags',\n ]),\n });\n\n // CloudWatch Log Group\n this.rules.set('AWS::Logs::LogGroup', {\n replacementProperties: new Set([\n 'LogGroupName', // Changing log group name requires replacement\n ]),\n updateableProperties: new Set(['RetentionInDays', 'KmsKeyId']),\n });\n\n // API Gateway RestApi\n this.rules.set('AWS::ApiGateway::RestApi', {\n replacementProperties: new Set([\n 'Name', // Changing API name can require replacement in some cases\n ]),\n updateableProperties: new Set([\n 'Description',\n 'Policy',\n 'EndpointConfiguration',\n 'BinaryMediaTypes',\n 'MinimumCompressionSize',\n 'Tags',\n ]),\n });\n\n // ECS Task Definition\n this.rules.set('AWS::ECS::TaskDefinition', {\n replacementProperties: new Set([\n // Task definitions are immutable - any change requires replacement\n 'Family',\n 'ContainerDefinitions',\n 'Cpu',\n 'Memory',\n 'NetworkMode',\n 'RequiresCompatibilities',\n 'ExecutionRoleArn',\n 'TaskRoleArn',\n 'Volumes',\n ]),\n });\n\n // Add more resource types as needed\n this.logger.debug(`Initialized replacement rules for ${this.rules.size} resource types`);\n }\n}\n","import type { CloudFormationTemplate, TemplateResource } from '../types/resource.js';\nimport type {\n StackState,\n ChangeType,\n ResourceChange,\n PropertyChange,\n AttributeChange,\n ResourceState,\n} from '../types/state.js';\nimport { getLogger } from '../utils/logger.js';\nimport { ReplacementRulesRegistry } from './replacement-rules.js';\n\n/**\n * Best-effort resolver for intrinsic functions during diff calculation.\n * Should return the resolved value on success, or the original value if resolution fails.\n * Kept as a callback to avoid circular dependency between analyzer and deployment layers.\n */\nexport type IntrinsicResolveFn = (value: unknown) => Promise<unknown>;\n\n/**\n * Diff calculator for comparing desired state (template) with current state\n */\nexport class DiffCalculator {\n private logger = getLogger().child('DiffCalculator');\n private replacementRules = new ReplacementRulesRegistry();\n\n /**\n * Calculate changes needed to reach desired state\n *\n * @param currentState Current stack state (use existing state or create a new StackState with empty resources for new stacks)\n * @param desiredTemplate Desired CloudFormation template\n * @param resolveFn Optional intrinsic resolver. When provided, desired properties are\n * resolved against current state before comparison so that changes\n * buried inside intrinsics (e.g. `Fn::Join` literal args) are detected.\n * If resolution throws for a given property value, the unresolved\n * value is used (falling back to the original \"assume equal\" behavior).\n * @returns Map of logical ID to resource change\n */\n async calculateDiff(\n currentState: StackState,\n desiredTemplate: CloudFormationTemplate,\n resolveFn?: IntrinsicResolveFn\n ): Promise<Map<string, ResourceChange>> {\n const changes = new Map<string, ResourceChange>();\n\n const currentResources = currentState.resources;\n const desiredResources = desiredTemplate.Resources;\n\n this.logger.debug('Calculating diff...');\n this.logger.debug(`Current resources: ${Object.keys(currentResources).length}`);\n this.logger.debug(`Desired resources: ${Object.keys(desiredResources).length}`);\n\n // Track which resources we've seen\n const processedLogicalIds = new Set<string>();\n\n // Check for CREATE and UPDATE\n for (const [logicalId, desiredResource] of Object.entries(desiredResources)) {\n // Skip CDK metadata resources (they don't actually deploy anything)\n if (desiredResource.Type === 'AWS::CDK::Metadata') {\n this.logger.debug(`Skipping metadata resource: ${logicalId}`);\n processedLogicalIds.add(logicalId);\n continue;\n }\n\n processedLogicalIds.add(logicalId);\n\n const currentResource = currentResources[logicalId];\n\n if (!currentResource) {\n // Resource doesn't exist in current state -> CREATE\n changes.set(logicalId, {\n logicalId,\n changeType: 'CREATE',\n resourceType: desiredResource.Type,\n desiredProperties: desiredResource.Properties || {},\n });\n this.logger.debug(`CREATE: ${logicalId} (${desiredResource.Type})`);\n } else if (currentResource.resourceType !== desiredResource.Type) {\n // Resource type changed -> requires replacement (DELETE + CREATE)\n // For simplicity, we'll mark this as UPDATE with requiresReplacement\n const propertyChanges: PropertyChange[] = [\n {\n path: 'Type',\n oldValue: currentResource.resourceType,\n newValue: desiredResource.Type,\n requiresReplacement: true,\n },\n ];\n\n changes.set(logicalId, {\n logicalId,\n changeType: 'UPDATE',\n resourceType: desiredResource.Type,\n currentProperties: currentResource.properties,\n desiredProperties: desiredResource.Properties || {},\n propertyChanges,\n });\n this.logger.debug(\n `UPDATE (Type change): ${logicalId} (${currentResource.resourceType} -> ${desiredResource.Type})`\n );\n } else {\n // Resource exists with same type -> check properties.\n //\n // State stores already-resolved values (e.g. \"my-bucket-value\"), while the\n // template holds unresolved intrinsics (e.g. { \"Fn::Join\": [...] }). When an\n // intrinsic wraps literal content that changed (e.g. \"-value\" -> \"-value2\"),\n // a naive comparison would short-circuit on the intrinsic node and miss the\n // change. Resolving desired props against current state first avoids that.\n const rawDesiredProps = desiredResource.Properties || {};\n const desiredPropsForCompare = resolveFn\n ? await this.resolveBestEffort(rawDesiredProps, resolveFn)\n : rawDesiredProps;\n\n const propertyChanges = this.compareProperties(\n desiredResource.Type,\n currentResource.properties,\n desiredPropsForCompare\n );\n\n // Schema v5+ template-attribute diff: `DeletionPolicy` /\n // `UpdateReplacePolicy` may change without any property change. cdkd\n // pre-v5 silently reported `No changes detected` for those, so a\n // user who removed `RemovalPolicy.DESTROY` from their CDK code saw\n // nothing happen on the next deploy. Detect them here too so the\n // attribute flip is surfaced (and the deploy engine refreshes the\n // value in state).\n const attributeChanges = this.compareAttributes(currentResource, desiredResource);\n\n if (propertyChanges.length > 0 || attributeChanges.length > 0) {\n // Property and/or attribute changed -> UPDATE\n changes.set(logicalId, {\n logicalId,\n changeType: 'UPDATE',\n resourceType: desiredResource.Type,\n currentProperties: currentResource.properties,\n desiredProperties: rawDesiredProps,\n propertyChanges,\n ...(attributeChanges.length > 0 && { attributeChanges }),\n });\n this.logger.debug(\n `UPDATE: ${logicalId} (${propertyChanges.length} property changes, ${attributeChanges.length} attribute changes)`\n );\n } else {\n // No changes -> NO_CHANGE\n changes.set(logicalId, {\n logicalId,\n changeType: 'NO_CHANGE',\n resourceType: desiredResource.Type,\n currentProperties: currentResource.properties,\n desiredProperties: rawDesiredProps,\n });\n this.logger.debug(`NO_CHANGE: ${logicalId}`);\n }\n }\n }\n\n // Check for DELETE (resources in current state but not in desired template)\n for (const [logicalId, currentResource] of Object.entries(currentResources)) {\n if (!processedLogicalIds.has(logicalId)) {\n changes.set(logicalId, {\n logicalId,\n changeType: 'DELETE',\n resourceType: currentResource.resourceType,\n currentProperties: currentResource.properties,\n });\n this.logger.debug(`DELETE: ${logicalId} (${currentResource.resourceType})`);\n }\n }\n\n const summary = this.getSummary(changes);\n this.logger.debug(\n `Diff calculated: ${summary.create} CREATE, ${summary.update} UPDATE, ${summary.delete} DELETE, ${summary.noChange} NO_CHANGE`\n );\n\n return changes;\n }\n\n /**\n * Best-effort resolution of template property intrinsics against current state.\n *\n * Iterates top-level properties and resolves each independently: if resolution\n * throws (e.g. Ref to a resource that isn't in state yet), the original value\n * is kept so downstream comparison falls back to the \"assume intrinsic equals\n * anything\" behavior for that one value instead of failing the whole diff.\n */\n private async resolveBestEffort(\n properties: Record<string, unknown>,\n resolveFn: IntrinsicResolveFn\n ): Promise<Record<string, unknown>> {\n const resolved: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n try {\n resolved[key] = await resolveFn(value);\n } catch {\n resolved[key] = value;\n }\n }\n return resolved;\n }\n\n /**\n * Compare CloudFormation template-level attributes (`DeletionPolicy`,\n * `UpdateReplacePolicy`) between cdkd state and the synth template.\n *\n * Schema v5+ records these in `ResourceState`; state written by an older\n * cdkd binary has the fields undefined. Treating `undefined === undefined`\n * as \"no change\" means the first post-upgrade deploy of an unchanged\n * template doesn't spuriously fire an attribute diff.\n */\n private compareAttributes(\n currentResource: ResourceState,\n desiredResource: TemplateResource\n ): AttributeChange[] {\n const changes: AttributeChange[] = [];\n if (currentResource.deletionPolicy !== desiredResource.DeletionPolicy) {\n changes.push({\n attribute: 'DeletionPolicy',\n oldValue: currentResource.deletionPolicy,\n newValue: desiredResource.DeletionPolicy,\n });\n }\n if (currentResource.updateReplacePolicy !== desiredResource.UpdateReplacePolicy) {\n changes.push({\n attribute: 'UpdateReplacePolicy',\n oldValue: currentResource.updateReplacePolicy,\n newValue: desiredResource.UpdateReplacePolicy,\n });\n }\n return changes;\n }\n\n /**\n * Compare properties and return list of changes\n *\n * Uses ReplacementRulesRegistry to determine which property changes require replacement.\n * Reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html\n */\n private compareProperties(\n resourceType: string,\n currentProperties: Record<string, unknown>,\n desiredProperties: Record<string, unknown>\n ): PropertyChange[] {\n const changes: PropertyChange[] = [];\n\n // Get all property keys\n const allKeys = new Set([...Object.keys(currentProperties), ...Object.keys(desiredProperties)]);\n\n // Properties to ignore in diff (non-deterministic, changes on every synth)\n const ignoredProperties = new Set<string>();\n if (\n resourceType === 'AWS::CloudFormation::CustomResource' ||\n resourceType.startsWith('Custom::')\n ) {\n ignoredProperties.add('Timestamp');\n }\n\n for (const key of allKeys) {\n if (ignoredProperties.has(key)) continue;\n\n const oldValue = currentProperties[key];\n const newValue = desiredProperties[key];\n\n if (!this.valuesEqual(oldValue, newValue)) {\n // Check if this property change requires replacement\n const requiresReplacement = this.replacementRules.requiresReplacement(\n resourceType,\n key,\n oldValue,\n newValue\n );\n\n changes.push({\n path: key,\n oldValue,\n newValue,\n requiresReplacement,\n });\n\n if (requiresReplacement) {\n this.logger.debug(\n `Property ${key} of ${resourceType} requires replacement (${JSON.stringify(oldValue)} -> ${JSON.stringify(newValue)})`\n );\n }\n }\n }\n\n return changes;\n }\n\n private static readonly INTRINSIC_KEYS = new Set([\n 'Ref',\n 'Fn::Sub',\n 'Fn::GetAtt',\n 'Fn::Join',\n 'Fn::Select',\n 'Fn::Split',\n 'Fn::If',\n 'Fn::ImportValue',\n 'Fn::FindInMap',\n 'Fn::Base64',\n 'Fn::GetAZs',\n 'Fn::Equals',\n 'Fn::And',\n 'Fn::Or',\n 'Fn::Not',\n ]);\n\n /**\n * Check if a value is itself a CloudFormation intrinsic function.\n * e.g. { \"Ref\": \"MyResource\" } or { \"Fn::GetAtt\": [\"Res\", \"Arn\"] }\n * Does NOT match objects that merely contain intrinsics as nested children.\n */\n private static isIntrinsic(value: unknown): boolean {\n if (\n value === null ||\n value === undefined ||\n typeof value !== 'object' ||\n Array.isArray(value)\n ) {\n return false;\n }\n const keys = Object.keys(value as Record<string, unknown>);\n return keys.length === 1 && DiffCalculator.INTRINSIC_KEYS.has(keys[0]!);\n }\n\n /**\n * Deep equality check for values\n *\n * When comparing state (resolved values) with template (unresolved intrinsics),\n * treats intrinsic function nodes as \"not comparable\" and assumes equal.\n * This check happens at each level of recursion, so only the specific value\n * that IS an intrinsic gets skipped — sibling values are still compared normally.\n *\n * Example: { Variables: { AZURE_REGION: \"japaneast\", SECRET_NAME: { \"Fn::Join\": ... } } }\n * - AZURE_REGION: compared normally (string vs string)\n * - SECRET_NAME: one side is intrinsic → treated as equal (skip)\n */\n private valuesEqual(a: unknown, b: unknown): boolean {\n // Strict equality check\n if (a === b) {\n return true;\n }\n\n // Null/undefined check\n if (a == null || b == null) {\n return a === b;\n }\n\n // If either side is an intrinsic function node, we can't compare\n // (state has resolved value like \"arn:...\", template has { \"Fn::GetAtt\": [...] })\n if (DiffCalculator.isIntrinsic(a) || DiffCalculator.isIntrinsic(b)) {\n return true;\n }\n\n // Array check\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) {\n return false;\n }\n return a.every((val, index) => this.valuesEqual(val, b[index]));\n }\n\n // Object check — recurse into each key so intrinsics are detected per-value\n if (typeof a === 'object' && typeof b === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n\n const bKeys = Object.keys(bObj);\n\n // Check keys in new (template) side exist in old (state) side with equal values.\n // Keys only in old side are ignored — they are typically AWS-added defaults\n // (e.g., IncludeCookies, Enabled, Prefix in CloudFront Logging) that don't\n // appear in the template but get stored in state after deployment.\n // Keys only in new side are real additions and will cause inequality.\n for (const key of bKeys) {\n if (!(key in aObj)) {\n return false; // New key added in template\n }\n if (!this.valuesEqual(aObj[key], bObj[key])) {\n return false;\n }\n }\n return true;\n }\n\n // Primitive types\n return false;\n }\n\n /**\n * Get summary of changes\n */\n getSummary(changes: Map<string, ResourceChange>): {\n create: number;\n update: number;\n delete: number;\n noChange: number;\n total: number;\n } {\n const summary = {\n create: 0,\n update: 0,\n delete: 0,\n noChange: 0,\n total: changes.size,\n };\n\n for (const change of changes.values()) {\n switch (change.changeType) {\n case 'CREATE':\n summary.create++;\n break;\n case 'UPDATE':\n summary.update++;\n break;\n case 'DELETE':\n summary.delete++;\n break;\n case 'NO_CHANGE':\n summary.noChange++;\n break;\n }\n }\n\n return summary;\n }\n\n /**\n * Filter changes by type\n */\n filterByType(changes: Map<string, ResourceChange>, type: ChangeType): ResourceChange[] {\n return Array.from(changes.values()).filter((change) => change.changeType === type);\n }\n\n /**\n * Check if there are any changes\n */\n hasChanges(changes: Map<string, ResourceChange>): boolean {\n return Array.from(changes.values()).some((change) => change.changeType !== 'NO_CHANGE');\n }\n\n /**\n * Get changes that require replacement\n */\n getReplacementChanges(changes: Map<string, ResourceChange>): ResourceChange[] {\n return Array.from(changes.values()).filter(\n (change) =>\n change.changeType === 'UPDATE' &&\n change.propertyChanges?.some((pc) => pc.requiresReplacement)\n );\n }\n}\n","import { GetCallerIdentityCommand } from '@aws-sdk/client-sts';\nimport {\n DescribeAvailabilityZonesCommand,\n DescribeLaunchTemplatesCommand,\n} from '@aws-sdk/client-ec2';\nimport { GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';\nimport { GetParameterCommand } from '@aws-sdk/client-ssm';\nimport { getLogger } from '../utils/logger.js';\nimport { getAwsClients } from '../utils/aws-clients.js';\nimport { stringifyValue } from '../utils/stringify.js';\nimport type { CloudFormationTemplate } from '../types/resource.js';\nimport type { ResourceState, StateImportEntry } from '../types/state.js';\nimport type { S3StateBackend } from '../state/s3-state-backend.js';\nimport type { ExportIndexStore } from '../state/export-index-store.js';\n\n/**\n * Special symbol to represent AWS::NoValue\n *\n * When a property resolves to this symbol, it should be removed from the object.\n * This is used for conditional property omission in CloudFormation templates.\n */\nexport const AWS_NO_VALUE = Symbol('AWS::NoValue');\n\n/**\n * Resolver context for intrinsic functions\n */\nexport interface ResolverContext {\n /** Template being processed */\n template: CloudFormationTemplate;\n /** Current resource states (for Ref/GetAtt) */\n resources: Record<string, ResourceState>;\n /** Parameter values (for Ref to parameters) */\n parameters?: Record<string, unknown>;\n /** Evaluated condition values (for Fn::If) */\n conditions?: Record<string, boolean>;\n /** State backend for cross-stack references (Fn::ImportValue) */\n stateBackend?: S3StateBackend;\n /** Current stack name (for Fn::ImportValue to avoid self-reference) */\n stackName?: string;\n /**\n * Persistent exports index for fast `Fn::ImportValue` resolution. When\n * supplied, the resolver tries an O(1) index lookup before falling back\n * to the per-stack state.json scan. Optional for backwards compat; the\n * scan-only path is still correct.\n */\n exportIndex?: ExportIndexStore;\n /**\n * Bag for the resolver to push every successful `Fn::ImportValue`\n * resolution into. The deploy engine reads this after resource\n * provisioning and persists it to the consumer's `state.imports`\n * field (schema v4) so destroy-time strong-reference checks can\n * refuse to delete a producer with active consumers.\n *\n * `Fn::GetStackOutput` does NOT push entries here by design — it is\n * a weak reference (see CLAUDE.md \"Behavior vs CDK\").\n */\n recordedImports?: StateImportEntry[];\n}\n\n/**\n * CloudFormation Intrinsic Function Resolver\n *\n * Resolves CloudFormation intrinsic functions in template values before\n * sending them to Cloud Control API or SDK providers.\n *\n * Supported functions:\n * - Ref (resources and parameters)\n * - Fn::GetAtt\n * - Fn::Join\n * - Fn::Sub\n * - Fn::Select\n * - Fn::Split\n * - Fn::If (Conditions)\n * - Fn::Equals\n * - Fn::And (logical AND)\n * - Fn::Or (logical OR)\n * - Fn::Not (logical NOT)\n * - Fn::ImportValue (cross-stack references)\n * - Fn::GetStackOutput (cross-stack/cross-region output reference)\n * - Fn::FindInMap (mapping lookups)\n * - Fn::Base64 (base64 encoding)\n * - Fn::GetAZs (availability zone listing)\n * - Fn::Cidr (CIDR address block calculation)\n */\n/**\n * AWS Account information cache\n */\ninterface AwsAccountInfo {\n accountId: string;\n region: string;\n partition: string;\n}\n\nlet cachedAccountInfo: AwsAccountInfo | null = null;\n\n/**\n * Cache for availability zones per region\n */\nconst cachedAvailabilityZones: Record<string, string[]> = {};\n\n/**\n * Cache for resolved dynamic references (secretsmanager, ssm)\n */\nconst cachedDynamicReferences: Record<string, string> = {};\n\n/**\n * Get AWS account information from STS\n */\nexport async function getAccountInfo(overrideRegion?: string): Promise<AwsAccountInfo> {\n if (cachedAccountInfo) {\n // If an override region is provided, return with that region\n if (overrideRegion && overrideRegion !== cachedAccountInfo.region) {\n return { ...cachedAccountInfo, region: overrideRegion };\n }\n return cachedAccountInfo;\n }\n\n const logger = getLogger().child('IntrinsicFunctionResolver');\n const awsClients = getAwsClients();\n const stsClient = awsClients.sts;\n\n try {\n const response = await stsClient.send(new GetCallerIdentityCommand({}));\n const accountId = response.Account || '123456789012';\n const region = overrideRegion || process.env['AWS_REGION'] || 'us-east-1';\n const partition = 'aws'; // Could be aws-cn, aws-us-gov, etc.\n\n cachedAccountInfo = { accountId, region, partition };\n logger.debug(`Retrieved AWS account info: ${accountId}, ${region}, ${partition}`);\n // Return with override if different from cached\n if (overrideRegion && overrideRegion !== region) {\n return { ...cachedAccountInfo, region: overrideRegion };\n }\n return cachedAccountInfo;\n } catch (error) {\n logger.warn(\n `Failed to get AWS account info from STS: ${error instanceof Error ? error.message : String(error)}, using defaults`\n );\n // Fallback to environment variables or defaults\n cachedAccountInfo = {\n accountId: process.env['AWS_ACCOUNT_ID'] || '123456789012',\n region: overrideRegion || process.env['AWS_REGION'] || 'us-east-1',\n partition: 'aws',\n };\n return cachedAccountInfo;\n }\n}\n\n/**\n * Reset cached account info (useful for testing)\n */\nexport function resetAccountInfoCache(): void {\n cachedAccountInfo = null;\n // Also reset AZ cache\n for (const key of Object.keys(cachedAvailabilityZones)) {\n delete cachedAvailabilityZones[key];\n }\n // Also reset dynamic reference cache\n for (const key of Object.keys(cachedDynamicReferences)) {\n delete cachedDynamicReferences[key];\n }\n}\n\n/**\n * CloudFormation Parameter definition\n */\nexport interface ParameterDefinition {\n Type: string;\n Default?: unknown;\n AllowedValues?: unknown[];\n AllowedPattern?: string;\n MinLength?: number;\n MaxLength?: number;\n MinValue?: number;\n MaxValue?: number;\n Description?: string;\n ConstraintDescription?: string;\n NoEcho?: boolean;\n}\n\nexport class IntrinsicFunctionResolver {\n private logger = getLogger().child('IntrinsicFunctionResolver');\n private readonly resolverRegion: string;\n\n constructor(region?: string) {\n this.resolverRegion = region || process.env['AWS_REGION'] || 'us-east-1';\n }\n\n /**\n * Resolve parameter values from template Parameters section\n *\n * Merges default values from template with user-provided parameter values.\n * User-provided values take precedence over defaults.\n *\n * @param template CloudFormation template containing Parameters section\n * @param userParameters User-provided parameter values (e.g., from CLI)\n * @returns Record of parameter names to resolved values\n */\n async resolveParameters(\n template: CloudFormationTemplate,\n userParameters?: Record<string, string>\n ): Promise<Record<string, unknown>> {\n const parameters: Record<string, unknown> = {};\n const templateParameters = template.Parameters;\n\n if (!templateParameters || typeof templateParameters !== 'object') {\n return parameters;\n }\n\n for (const [name, definition] of Object.entries(templateParameters)) {\n const paramDef = definition as ParameterDefinition;\n\n // User-provided value takes precedence\n if (userParameters && name in userParameters) {\n const userValue = userParameters[name];\n if (userValue !== undefined) {\n parameters[name] = this.coerceParameterValue(userValue, paramDef.Type);\n this.logger.debug(`Parameter ${name}: using user-provided value ${userValue}`);\n continue;\n }\n }\n\n // Use default value if available\n if ('Default' in paramDef) {\n // SSM Parameter type: resolve the default value (SSM parameter path) via SSM API\n if (paramDef.Type.startsWith('AWS::SSM::Parameter::Value')) {\n const ssmPath = String(paramDef.Default);\n this.logger.debug(`Parameter ${name}: resolving SSM parameter path ${ssmPath}`);\n const resolved = await this.resolveSSMParameter(ssmPath);\n parameters[name] = resolved;\n this.logger.debug(`Parameter ${name}: resolved SSM value ${resolved}`);\n continue;\n }\n\n parameters[name] = paramDef.Default;\n this.logger.debug(\n `Parameter ${name}: using default value ${stringifyValue(paramDef.Default)}`\n );\n continue;\n }\n\n // No value provided and no default - this is an error\n throw new Error(\n `Parameter ${name} is required but no value was provided and no default exists`\n );\n }\n\n return parameters;\n }\n\n /**\n * Resolve an SSM Parameter Store path to its actual value.\n * Used for parameters with type AWS::SSM::Parameter::Value<...>.\n */\n private async resolveSSMParameter(parameterName: string): Promise<string> {\n const client = getAwsClients().ssm;\n const response = await client.send(new GetParameterCommand({ Name: parameterName }));\n return response.Parameter?.Value ?? '';\n }\n\n /**\n * Coerce parameter value to the correct type based on parameter definition\n */\n private coerceParameterValue(value: string, type: string): unknown {\n switch (type) {\n case 'Number':\n return Number(value);\n case 'List<Number>':\n return value.split(',').map((v) => Number(v.trim()));\n case 'CommaDelimitedList':\n return value.split(',').map((v) => v.trim());\n case 'String':\n default:\n return value;\n }\n }\n\n /**\n * Resolve all intrinsic functions in a value\n */\n async resolve(value: unknown, context: ResolverContext): Promise<unknown> {\n return await this.resolveValue(value, context);\n }\n\n /**\n * Evaluate all conditions in the template\n *\n * Conditions are defined in the Conditions section of the CloudFormation template\n * and can reference parameters and pseudo parameters\n */\n async evaluateConditions(context: ResolverContext): Promise<Record<string, boolean>> {\n const conditions: Record<string, boolean> = {};\n const templateConditions = context.template.Conditions;\n\n if (!templateConditions || typeof templateConditions !== 'object') {\n return conditions;\n }\n\n // Evaluate each condition\n for (const [name, definition] of Object.entries(templateConditions)) {\n try {\n const result = await this.resolveValue(definition, context);\n conditions[name] = Boolean(result);\n this.logger.debug(`Evaluated condition ${name} = ${conditions[name]}`);\n } catch (error) {\n this.logger.warn(\n `Failed to evaluate condition ${name}: ${error instanceof Error ? error.message : String(error)}, assuming false`\n );\n conditions[name] = false;\n }\n }\n\n return conditions;\n }\n\n /**\n * Recursively resolve a value\n */\n private async resolveValue(value: unknown, context: ResolverContext): Promise<unknown> {\n // Primitives: return as-is (but check strings for dynamic references)\n if (typeof value !== 'object' || value === null) {\n if (typeof value === 'string' && value.includes('{{resolve:')) {\n return await this.resolveDynamicReferences(value);\n }\n return value;\n }\n\n // Arrays: resolve each element, filtering out AWS::NoValue\n if (Array.isArray(value)) {\n const resolved = await Promise.all(value.map((v) => this.resolveValue(v, context)));\n return resolved.filter((v) => v !== AWS_NO_VALUE);\n }\n\n const obj = value as Record<string, unknown>;\n\n // Check for intrinsic functions\n if ('Ref' in obj) {\n return await this.resolveRef(obj['Ref'] as string, context);\n }\n\n if ('Fn::GetAtt' in obj) {\n return await this.resolveGetAtt(obj['Fn::GetAtt'] as [string, string] | string, context);\n }\n\n if ('Fn::Join' in obj) {\n return await this.resolveJoin(obj['Fn::Join'] as [string, unknown[]], context);\n }\n\n if ('Fn::Sub' in obj) {\n return await this.resolveSub(\n obj['Fn::Sub'] as string | [string, Record<string, unknown>],\n context\n );\n }\n\n if ('Fn::Select' in obj) {\n return await this.resolveSelect(obj['Fn::Select'] as [number, unknown[]], context);\n }\n\n if ('Fn::Split' in obj) {\n return await this.resolveSplit(obj['Fn::Split'] as [string, unknown], context);\n }\n\n if ('Fn::If' in obj) {\n return await this.resolveIf(obj['Fn::If'] as [string, unknown, unknown], context);\n }\n\n if ('Fn::Equals' in obj) {\n return await this.resolveEquals(obj['Fn::Equals'] as [unknown, unknown], context);\n }\n\n if ('Fn::And' in obj) {\n return await this.resolveAnd(obj['Fn::And'] as unknown[], context);\n }\n\n if ('Fn::Or' in obj) {\n return await this.resolveOr(obj['Fn::Or'] as unknown[], context);\n }\n\n if ('Fn::Not' in obj) {\n return await this.resolveNot(obj['Fn::Not'] as [unknown], context);\n }\n\n if ('Fn::ImportValue' in obj) {\n return await this.resolveImportValue(obj['Fn::ImportValue'], context);\n }\n\n if ('Fn::GetStackOutput' in obj) {\n return await this.resolveGetStackOutput(obj['Fn::GetStackOutput'], context);\n }\n\n if ('Fn::FindInMap' in obj) {\n return await this.resolveFindInMap(\n obj['Fn::FindInMap'] as [unknown, unknown, unknown],\n context\n );\n }\n\n if ('Fn::Base64' in obj) {\n return await this.resolveBase64(obj['Fn::Base64'], context);\n }\n\n if ('Fn::GetAZs' in obj) {\n return await this.resolveGetAZs(obj['Fn::GetAZs'], context);\n }\n\n if ('Fn::Cidr' in obj) {\n return await this.resolveCidr(obj['Fn::Cidr'] as [unknown, unknown, unknown], context);\n }\n\n // Not an intrinsic function: recursively resolve object properties\n const resolved: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(obj)) {\n const resolvedVal = await this.resolveValue(val, context);\n // Skip properties that resolve to AWS::NoValue\n if (resolvedVal !== AWS_NO_VALUE) {\n resolved[key] = resolvedVal;\n } else {\n this.logger.debug(`Property ${key} resolved to AWS::NoValue, omitting from object`);\n }\n }\n return resolved;\n }\n\n /**\n * Resolve Ref intrinsic function\n *\n * Ref can reference:\n * 1. Resources (returns physical ID)\n * 2. Parameters (returns parameter value)\n * 3. Pseudo parameters (AWS::Region, AWS::AccountId, etc.)\n */\n private async resolveRef(logicalId: string, context: ResolverContext): Promise<unknown> {\n // Check if it's a resource\n const resource = context.resources[logicalId];\n if (resource) {\n this.logger.debug(`Resolved Ref to resource: ${logicalId} -> ${resource.physicalId}`);\n return resource.physicalId;\n }\n\n // Check if it's a parameter\n if (context.parameters && logicalId in context.parameters) {\n const value = context.parameters[logicalId];\n this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${stringifyValue(value)}`);\n return value;\n }\n\n // Check if it's a pseudo parameter\n const pseudoValue = await this.resolvePseudoParameter(logicalId, context);\n if (pseudoValue !== undefined) {\n const valueStr =\n typeof pseudoValue === 'symbol' ? pseudoValue.toString() : String(pseudoValue);\n this.logger.debug(`Resolved Ref to pseudo parameter: ${logicalId} -> ${valueStr}`);\n return pseudoValue;\n }\n\n // Not found\n this.logger.warn(`Ref ${logicalId} not found (not a resource, parameter, or pseudo parameter)`);\n throw new Error(`Ref ${logicalId} not found`);\n }\n\n /**\n * Resolve Fn::GetAtt intrinsic function\n */\n private async resolveGetAtt(\n getAtt: [string, string] | string,\n context: ResolverContext\n ): Promise<unknown> {\n // Fn::GetAtt can be either [LogicalId, AttributeName] or \"LogicalId.AttributeName\"\n let logicalId: string;\n let attributeName: string;\n\n if (Array.isArray(getAtt)) {\n [logicalId, attributeName] = getAtt;\n } else {\n const parts = getAtt.split('.');\n if (parts.length !== 2) {\n throw new Error(`Invalid Fn::GetAtt format: ${getAtt}`);\n }\n [logicalId, attributeName] = parts as [string, string];\n }\n\n const resource = context.resources[logicalId];\n if (!resource) {\n throw new Error(`Resource ${logicalId} not found for Fn::GetAtt`);\n }\n\n // Check if attribute exists in resource.attributes\n // For VPC Ipv6CidrBlocks, always use constructAttribute (dynamic fetch with retry)\n // because the stored value may be stale (empty array from before VPCCidrBlock association)\n const skipCachedAttribute =\n resource.resourceType === 'AWS::EC2::VPC' && attributeName === 'Ipv6CidrBlocks';\n\n if (!skipCachedAttribute && resource.attributes?.[attributeName] !== undefined) {\n const value = resource.attributes[attributeName];\n this.logger.debug(\n `Resolved Fn::GetAtt from attributes: ${logicalId}.${attributeName} -> ${stringifyValue(value)}`\n );\n return value;\n }\n\n // Construct attribute value based on resource type\n const value = await this.constructAttribute(resource, attributeName, context);\n this.logger.debug(\n `Resolved Fn::GetAtt: ${logicalId}.${attributeName} -> ${stringifyValue(value)}`\n );\n return value;\n }\n\n /**\n * Construct resource attribute value based on resource type\n *\n * Many CloudFormation attributes are not returned by Cloud Control API,\n * so we need to construct them manually.\n */\n private async constructAttribute(\n resource: ResourceState,\n attributeName: string,\n _context: ResolverContext\n ): Promise<unknown> {\n const { resourceType, physicalId } = resource;\n const accountInfo = await getAccountInfo(this.resolverRegion);\n const { region, accountId, partition } = accountInfo;\n\n // DynamoDB Table / GlobalTable (CDK TableV2 synthesizes as AWS::DynamoDB::GlobalTable; ARN format is identical)\n if (resourceType === 'AWS::DynamoDB::Table' || resourceType === 'AWS::DynamoDB::GlobalTable') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:dynamodb:${region}:${accountId}:table/${physicalId}`;\n case 'StreamArn':\n // Stream ARN would need to be fetched from API\n return undefined;\n default:\n return physicalId;\n }\n }\n\n // S3 Bucket\n if (resourceType === 'AWS::S3::Bucket') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:s3:::${physicalId}`;\n case 'DomainName':\n return `${physicalId}.s3.amazonaws.com`;\n case 'RegionalDomainName':\n return `${physicalId}.s3.${region}.amazonaws.com`;\n case 'WebsiteURL':\n return `http://${physicalId}.s3-website-${region}.amazonaws.com`;\n default:\n return physicalId;\n }\n }\n\n // IAM Role\n if (resourceType === 'AWS::IAM::Role') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:role/${physicalId}`;\n case 'RoleId':\n // Role ID would need to be fetched from API\n return undefined;\n default:\n return physicalId;\n }\n }\n\n // EC2 VPC - dynamic attributes (IPv6 CIDR requires DescribeVpcs after VPCCidrBlock association)\n if (resourceType === 'AWS::EC2::VPC') {\n switch (attributeName) {\n case 'VpcId':\n return physicalId;\n case 'CidrBlock':\n return resource.attributes?.['CidrBlock'] || resource.properties?.['CidrBlock'];\n case 'Ipv6CidrBlocks': {\n // Must fetch dynamically - IPv6 CIDR is added by VPCCidrBlock resource after VPC creation.\n // After CC API reports VPCCidrBlock CREATE success, the CIDR may still be in\n // 'associating' state. Retry up to 30s waiting for 'associated'.\n try {\n const { EC2Client, DescribeVpcsCommand } = await import('@aws-sdk/client-ec2');\n const ec2 = new EC2Client({ region: this.resolverRegion });\n const maxAttempts = 15;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const resp = await ec2.send(new DescribeVpcsCommand({ VpcIds: [physicalId] }));\n const associations = resp.Vpcs?.[0]?.Ipv6CidrBlockAssociationSet || [];\n const blocks = associations\n .filter((a) => a.Ipv6CidrBlockState?.State === 'associated')\n .map((a) => a.Ipv6CidrBlock);\n if (blocks.length > 0) {\n this.logger.debug(\n `Resolved VPC Ipv6CidrBlocks for ${physicalId}: ${JSON.stringify(blocks)}`\n );\n return blocks;\n }\n // Check if there are any associating CIDRs — if so, wait and retry\n const associating = associations.filter(\n (a) => a.Ipv6CidrBlockState?.State === 'associating'\n );\n if (associating.length === 0) {\n // No IPv6 CIDRs at all\n this.logger.debug(`No IPv6 CIDR associations found for VPC ${physicalId}`);\n return [];\n }\n this.logger.debug(\n `VPC ${physicalId} IPv6 CIDR still associating (attempt ${attempt}/${maxAttempts}), waiting...`\n );\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n this.logger.warn(\n `VPC ${physicalId} IPv6 CIDR did not reach 'associated' state after ${maxAttempts} attempts`\n );\n return [];\n } catch (error) {\n this.logger.warn(\n `Failed to fetch VPC Ipv6CidrBlocks for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n return [];\n }\n }\n case 'DefaultSecurityGroup':\n return resource.attributes?.['DefaultSecurityGroup'] || physicalId;\n default:\n return physicalId;\n }\n }\n\n // IAM Policy\n if (resourceType === 'AWS::IAM::Policy') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:policy/${physicalId}`;\n case 'PolicyId':\n // Policy ID would need to be fetched from API\n return undefined;\n default:\n return physicalId;\n }\n }\n\n // IAM User\n if (resourceType === 'AWS::IAM::User') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:user/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // IAM Group\n if (resourceType === 'AWS::IAM::Group') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:group/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // IAM InstanceProfile\n if (resourceType === 'AWS::IAM::InstanceProfile') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:iam::${accountId}:instance-profile/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // KMS Key\n if (resourceType === 'AWS::KMS::Key') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:kms:${region}:${accountId}:key/${physicalId}`;\n case 'KeyId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // Cognito UserPool\n if (resourceType === 'AWS::Cognito::UserPool') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:cognito-idp:${region}:${accountId}:userpool/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // Kinesis Stream\n if (resourceType === 'AWS::Kinesis::Stream') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:kinesis:${region}:${accountId}:stream/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // EventBridge Rule. Custom event bus ARN: rule/{busName}/{ruleName};\n // default bus ARN: rule/{ruleName}. By the time constructAttribute runs,\n // properties.EventBusName (if templated) has been resolved to a literal\n // string or ARN by the deploy engine. Treat 'default' / unset as default bus.\n if (resourceType === 'AWS::Events::Rule') {\n switch (attributeName) {\n case 'Arn': {\n const busRaw = resource.properties?.['EventBusName'];\n const bus = typeof busRaw === 'string' && busRaw && busRaw !== 'default' ? busRaw : '';\n // If EventBusName resolved to an ARN, extract the bus name segment\n const busName = bus.startsWith('arn:') ? bus.split('/').pop() || '' : bus;\n return busName\n ? `arn:${partition}:events:${region}:${accountId}:rule/${busName}/${physicalId}`\n : `arn:${partition}:events:${region}:${accountId}:rule/${physicalId}`;\n }\n default:\n return physicalId;\n }\n }\n\n // EventBridge EventBus\n if (resourceType === 'AWS::Events::EventBus') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:events:${region}:${accountId}:event-bus/${physicalId}`;\n case 'Name':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // EFS FileSystem\n if (resourceType === 'AWS::EFS::FileSystem') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:elasticfilesystem:${region}:${accountId}:file-system/${physicalId}`;\n case 'FileSystemId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // Kinesis Data Firehose DeliveryStream\n if (resourceType === 'AWS::KinesisFirehose::DeliveryStream') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:firehose:${region}:${accountId}:deliverystream/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // CodeBuild Project\n if (resourceType === 'AWS::CodeBuild::Project') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:codebuild:${region}:${accountId}:project/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // CloudTrail Trail\n if (resourceType === 'AWS::CloudTrail::Trail') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:cloudtrail:${region}:${accountId}:trail/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // AppSync GraphQLApi (physicalId is the apiId)\n if (resourceType === 'AWS::AppSync::GraphQLApi') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:appsync:${region}:${accountId}:apis/${physicalId}`;\n case 'ApiId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // ServiceDiscovery PrivateDnsNamespace (physicalId is the namespace id)\n if (resourceType === 'AWS::ServiceDiscovery::PrivateDnsNamespace') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:servicediscovery:${region}:${accountId}:namespace/${physicalId}`;\n case 'Id':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // ServiceDiscovery Service (physicalId is the service id)\n if (resourceType === 'AWS::ServiceDiscovery::Service') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:servicediscovery:${region}:${accountId}:service/${physicalId}`;\n case 'Id':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // CloudWatch Alarm (note: 'alarm:' separator, not '/')\n if (resourceType === 'AWS::CloudWatch::Alarm') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:cloudwatch:${region}:${accountId}:alarm:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // RDS DBInstance (DocDB and Neptune share the same rds: service prefix and db: separator)\n if (\n resourceType === 'AWS::RDS::DBInstance' ||\n resourceType === 'AWS::DocDB::DBInstance' ||\n resourceType === 'AWS::Neptune::DBInstance'\n ) {\n switch (attributeName) {\n case 'DBInstanceArn':\n case 'Arn':\n return `arn:${partition}:rds:${region}:${accountId}:db:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // RDS DBCluster (DocDB and Neptune share the same rds: service prefix and cluster: separator)\n if (\n resourceType === 'AWS::RDS::DBCluster' ||\n resourceType === 'AWS::DocDB::DBCluster' ||\n resourceType === 'AWS::Neptune::DBCluster'\n ) {\n switch (attributeName) {\n case 'DBClusterArn':\n case 'Arn':\n return `arn:${partition}:rds:${region}:${accountId}:cluster:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // S3 Express Directory Bucket\n if (resourceType === 'AWS::S3Express::DirectoryBucket') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:s3express:${region}:${accountId}:bucket/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // Lambda Function\n if (resourceType === 'AWS::Lambda::Function') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:lambda:${region}:${accountId}:function:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // SQS Queue\n if (resourceType === 'AWS::SQS::Queue') {\n // Physical ID for SQS Queue is the queue URL\n // Extract queue name from URL: https://sqs.region.amazonaws.com/accountId/queueName\n let queueName = physicalId;\n if (physicalId.startsWith('https://')) {\n const parts = physicalId.split('/');\n queueName = parts[parts.length - 1] || physicalId;\n }\n\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:sqs:${region}:${accountId}:${queueName}`;\n case 'QueueUrl':\n return physicalId; // Physical ID is already the queue URL\n case 'QueueName':\n return queueName;\n default:\n return physicalId;\n }\n }\n\n // SNS Topic\n if (resourceType === 'AWS::SNS::Topic') {\n switch (attributeName) {\n case 'TopicArn':\n return `arn:${partition}:sns:${region}:${accountId}:${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // CloudWatch Logs Log Group\n if (resourceType === 'AWS::Logs::LogGroup') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:logs:${region}:${accountId}:log-group:${physicalId}:*`;\n default:\n return physicalId;\n }\n }\n\n // ECR Repository\n if (resourceType === 'AWS::ECR::Repository') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:ecr:${region}:${accountId}:repository/${physicalId}`;\n case 'RepositoryUri':\n return `${accountId}.dkr.ecr.${region}.amazonaws.com/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // ECS Cluster\n if (resourceType === 'AWS::ECS::Cluster') {\n switch (attributeName) {\n case 'Arn':\n return `arn:${partition}:ecs:${region}:${accountId}:cluster/${physicalId}`;\n default:\n return physicalId;\n }\n }\n\n // EC2 Security Group\n if (resourceType === 'AWS::EC2::SecurityGroup') {\n switch (attributeName) {\n case 'GroupId':\n return physicalId; // Physical ID is already the group ID (sg-xxx)\n case 'VpcId':\n return undefined; // Would need API call\n default:\n return physicalId;\n }\n }\n\n // EC2 Subnet\n if (resourceType === 'AWS::EC2::Subnet') {\n switch (attributeName) {\n case 'SubnetId':\n return physicalId;\n default:\n return physicalId;\n }\n }\n\n // EC2 LaunchTemplate — `LatestVersionNumber` / `DefaultVersionNumber`\n // are AWS-derived integers that cdkd does not capture in state.\n // Resolve via `DescribeLaunchTemplates`. Return as a string so\n // downstream consumers (`AWS::AutoScaling::AutoScalingGroup`'s\n // `LaunchTemplate.Version`) get the form AWS accepts. Falling back\n // to the physical ID — as the previous default did — produced\n // `Invalid launch template version: either '$Default', '$Latest',\n // or a numeric version are allowed.` on `CreateAutoScalingGroup`.\n if (resourceType === 'AWS::EC2::LaunchTemplate') {\n if (attributeName === 'LatestVersionNumber' || attributeName === 'DefaultVersionNumber') {\n try {\n const clients = getAwsClients();\n const response = await clients.ec2.send(\n new DescribeLaunchTemplatesCommand({ LaunchTemplateIds: [physicalId] })\n );\n const lt = response.LaunchTemplates?.[0];\n const value =\n attributeName === 'LatestVersionNumber'\n ? lt?.LatestVersionNumber\n : lt?.DefaultVersionNumber;\n if (value !== undefined && value !== null) {\n return String(value);\n }\n } catch (err) {\n this.logger.warn(\n `DescribeLaunchTemplates(${physicalId}) failed for ${attributeName}: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n // Fallback to \"$Latest\" / \"$Default\" — both are AWS-accepted\n // strings for the corresponding semantic, and let AWS pick the\n // version at API call time. Better than the resource-id\n // physicalId fallback which AWS rejects.\n return attributeName === 'LatestVersionNumber' ? '$Latest' : '$Default';\n }\n return physicalId;\n }\n\n // Default: return physical ID\n this.logger.warn(\n `Unknown attribute ${attributeName} for resource type ${resourceType}, returning physical ID`\n );\n return physicalId;\n }\n\n /**\n * Resolve Fn::Join intrinsic function\n *\n * Fn::Join: [delimiter, [value1, value2, ...]]\n */\n private async resolveJoin(\n joinArgs: [string, unknown[]],\n context: ResolverContext\n ): Promise<string> {\n const [delimiter, values] = joinArgs;\n\n // Resolve each value first\n const resolvedValues = await Promise.all(\n values.map(async (v) => {\n const resolved = await this.resolveValue(v, context);\n return String(resolved);\n })\n );\n\n let result = resolvedValues.join(delimiter);\n // Resolve any dynamic references in the joined result\n if (result.includes('{{resolve:')) {\n result = await this.resolveDynamicReferences(result);\n }\n this.logger.debug(`Resolved Fn::Join: ${result}`);\n return result;\n }\n\n /**\n * Resolve Fn::Sub intrinsic function\n *\n * Fn::Sub supports two forms:\n * 1. String with ${VarName} placeholders\n * 2. [String, {VarName: value, ...}] with explicit variable mapping\n *\n * Note: This is a simplified implementation that doesn't handle async properly\n * inside replace(). For full async support, we'd need to collect all replacements\n * first, then do them synchronously.\n */\n private async resolveSub(\n subArgs: string | [string, Record<string, unknown>],\n context: ResolverContext\n ): Promise<string> {\n let template: string;\n let variables: Record<string, unknown> = {};\n\n if (Array.isArray(subArgs)) {\n [template, variables] = subArgs;\n // Resolve variable values\n for (const [key, val] of Object.entries(variables)) {\n variables[key] = await this.resolveValue(val, context);\n }\n } else {\n template = subArgs;\n }\n\n // Collect all replacements\n const replacements: Array<{ match: string; replacement: string }> = [];\n const matches = template.matchAll(/\\$\\{([^}]+)\\}/g);\n\n for (const match of matches) {\n const varNameStr = match[1];\n if (!varNameStr) {\n continue; // Skip if no capture group\n }\n\n let replacement: string;\n\n // Check explicit variables first\n if (varNameStr in variables) {\n replacement = String(variables[varNameStr]);\n } else {\n // Check if it's a pseudo parameter\n const pseudoValue = await this.resolvePseudoParameter(varNameStr, context);\n if (pseudoValue !== undefined) {\n replacement = String(pseudoValue);\n } else {\n // Try to resolve as Ref\n try {\n const value = await this.resolveRef(varNameStr, context);\n replacement = String(value);\n } catch {\n // If not found, try to resolve as GetAtt (e.g., \"Resource.Attribute\")\n if (varNameStr.includes('.')) {\n try {\n const value = await this.resolveGetAtt(varNameStr, context);\n replacement = String(value);\n } catch {\n this.logger.warn(`Fn::Sub variable ${varNameStr} not found, keeping placeholder`);\n replacement = match[0]; // Keep original placeholder\n }\n } else {\n this.logger.warn(`Fn::Sub variable ${varNameStr} not found, keeping placeholder`);\n replacement = match[0]; // Keep original placeholder\n }\n }\n }\n }\n\n replacements.push({ match: match[0], replacement });\n }\n\n // Apply all replacements\n let result = template;\n for (const { match, replacement } of replacements) {\n result = result.replace(match, replacement);\n }\n\n // Resolve any dynamic references in the substituted result\n if (result.includes('{{resolve:')) {\n result = await this.resolveDynamicReferences(result);\n }\n this.logger.debug(`Resolved Fn::Sub: ${result}`);\n return result;\n }\n\n /**\n * Resolve Fn::Select intrinsic function\n *\n * Fn::Select: [index, [value1, value2, ...]]\n * Returns the value at the specified index in the list\n */\n private async resolveSelect(\n selectArgs: [number, unknown[]],\n context: ResolverContext\n ): Promise<unknown> {\n const [index, list] = selectArgs;\n\n // Resolve the list first\n const resolvedList = await this.resolveValue(list, context);\n\n if (!Array.isArray(resolvedList)) {\n throw new Error(`Fn::Select: list must be an array, got ${typeof resolvedList}`);\n }\n\n if (index < 0 || index >= resolvedList.length) {\n this.logger.warn(\n `Fn::Select: index ${index} out of bounds (array length: ${resolvedList.length})`\n );\n return `{{Fn::Select:${index}:OutOfBounds}}`;\n }\n\n const result: unknown = resolvedList[index];\n this.logger.debug(`Resolved Fn::Select: index ${index} -> ${JSON.stringify(result)}`);\n return result;\n }\n\n /**\n * Resolve Fn::Split intrinsic function\n *\n * Fn::Split: [delimiter, string]\n * Splits a string into a list of strings using the specified delimiter\n */\n private async resolveSplit(\n splitArgs: [string, unknown],\n context: ResolverContext\n ): Promise<string[]> {\n const [delimiter, value] = splitArgs;\n\n // Resolve the value first\n const resolvedValue = await this.resolveValue(value, context);\n\n if (typeof resolvedValue !== 'string') {\n throw new Error(`Fn::Split: value must be a string, got ${typeof resolvedValue}`);\n }\n\n const result = resolvedValue.split(delimiter);\n this.logger.debug(`Resolved Fn::Split: split by \"${delimiter}\" -> ${JSON.stringify(result)}`);\n return result;\n }\n\n /**\n * Resolve Fn::If intrinsic function\n *\n * Fn::If: [conditionName, valueIfTrue, valueIfFalse]\n * Returns valueIfTrue if condition evaluates to true, otherwise valueIfFalse\n */\n private async resolveIf(\n ifArgs: [string, unknown, unknown],\n context: ResolverContext\n ): Promise<unknown> {\n const [conditionName, valueIfTrue, valueIfFalse] = ifArgs;\n\n // Check if condition is evaluated in context\n if (!context.conditions || !(conditionName in context.conditions)) {\n this.logger.warn(`Condition ${conditionName} not found in context, assuming false`);\n return await this.resolveValue(valueIfFalse, context);\n }\n\n const conditionValue = context.conditions[conditionName];\n const selectedValue = conditionValue ? valueIfTrue : valueIfFalse;\n\n this.logger.debug(\n `Resolved Fn::If: condition ${conditionName} = ${conditionValue}, selected ${conditionValue ? 'true' : 'false'} branch`\n );\n\n return await this.resolveValue(selectedValue, context);\n }\n\n /**\n * Resolve Fn::Equals intrinsic function\n *\n * Fn::Equals: [value1, value2]\n * Returns true if both values are equal after resolution\n */\n private async resolveEquals(\n equalsArgs: [unknown, unknown],\n context: ResolverContext\n ): Promise<boolean> {\n const [value1, value2] = equalsArgs;\n\n // Resolve both values\n const resolved1 = await this.resolveValue(value1, context);\n const resolved2 = await this.resolveValue(value2, context);\n\n // Deep equality check\n const result = JSON.stringify(resolved1) === JSON.stringify(resolved2);\n\n this.logger.debug(\n `Resolved Fn::Equals: ${JSON.stringify(resolved1)} === ${JSON.stringify(resolved2)} -> ${result}`\n );\n\n return result;\n }\n\n /**\n * Resolve Fn::And intrinsic function\n *\n * Returns true if all conditions evaluate to true\n * Syntax: { \"Fn::And\": [ condition1, condition2, ... ] }\n */\n private async resolveAnd(conditions: unknown[], context: ResolverContext): Promise<boolean> {\n if (!Array.isArray(conditions) || conditions.length < 2 || conditions.length > 10) {\n throw new Error(`Fn::And requires between 2 and 10 conditions, got ${conditions.length}`);\n }\n\n // Resolve all conditions\n const results: boolean[] = [];\n for (const condition of conditions) {\n const resolved = await this.resolveValue(condition, context);\n results.push(Boolean(resolved));\n }\n\n // Return true if all are true\n const result = results.every((r) => r === true);\n\n this.logger.debug(`Resolved Fn::And: [${results.join(', ')}] -> ${result}`);\n\n return result;\n }\n\n /**\n * Resolve Fn::Or intrinsic function\n *\n * Returns true if at least one condition evaluates to true\n * Syntax: { \"Fn::Or\": [ condition1, condition2, ... ] }\n */\n private async resolveOr(conditions: unknown[], context: ResolverContext): Promise<boolean> {\n if (!Array.isArray(conditions) || conditions.length < 2 || conditions.length > 10) {\n throw new Error(`Fn::Or requires between 2 and 10 conditions, got ${conditions.length}`);\n }\n\n // Resolve all conditions\n const results: boolean[] = [];\n for (const condition of conditions) {\n const resolved = await this.resolveValue(condition, context);\n results.push(Boolean(resolved));\n }\n\n // Return true if at least one is true\n const result = results.some((r) => r === true);\n\n this.logger.debug(`Resolved Fn::Or: [${results.join(', ')}] -> ${result}`);\n\n return result;\n }\n\n /**\n * Resolve Fn::Not intrinsic function\n *\n * Returns the inverse of the condition\n * Syntax: { \"Fn::Not\": [ condition ] }\n */\n private async resolveNot(notArgs: [unknown], context: ResolverContext): Promise<boolean> {\n if (!Array.isArray(notArgs) || notArgs.length !== 1) {\n throw new Error(\n `Fn::Not requires exactly one condition, got ${Array.isArray(notArgs) ? notArgs.length : 0}`\n );\n }\n\n const [condition] = notArgs;\n\n // Resolve the condition\n const resolved = await this.resolveValue(condition, context);\n const result = !resolved;\n\n this.logger.debug(`Resolved Fn::Not: ${Boolean(resolved)} -> ${result}`);\n\n return result;\n }\n\n /**\n * Resolve Fn::ImportValue (cross-stack references)\n *\n * Searches all other stacks for an exported output with the given name.\n */\n private async resolveImportValue(\n importValueArg: unknown,\n context: ResolverContext\n ): Promise<unknown> {\n // First, resolve the export name (it might contain intrinsic functions)\n const exportName = await this.resolveValue(importValueArg, context);\n\n if (typeof exportName !== 'string') {\n throw new Error(\n `Fn::ImportValue: export name must resolve to a string, got ${typeof exportName}`\n );\n }\n\n // Check if we have a state backend\n if (!context.stateBackend) {\n throw new Error('Fn::ImportValue: state backend is required for cross-stack references');\n }\n\n this.logger.debug(`Resolving Fn::ImportValue: ${exportName}`);\n\n // Hot path: consult the persistent exports index for O(1) lookup.\n // Skip self-references (a stack importing its own export) so the\n // fallback scan below can apply the same exclusion.\n if (context.exportIndex) {\n try {\n const entry = await context.exportIndex.lookup(exportName);\n if (entry && (!context.stackName || entry.producerStack !== context.stackName)) {\n this.recordImport(context, exportName, entry.producerStack, entry.producerRegion);\n this.logger.info(\n `Resolved Fn::ImportValue: ${exportName} = ${JSON.stringify(entry.value)} (from index: ${entry.producerStack} / ${entry.producerRegion})`\n );\n return entry.value;\n }\n } catch (err) {\n this.logger.warn(\n `Exports index lookup failed for '${exportName}': ${err instanceof Error ? err.message : String(err)}; falling back to state.json scan`\n );\n }\n }\n\n // Fallback path (index miss, drift, or no index supplied): scan every\n // stack's state.json. Same as the pre-index behavior.\n const allStacks = await context.stateBackend.listStacks();\n this.logger.debug(\n `Found ${allStacks.length} state record(s) to search for export: ${exportName}`\n );\n\n for (const ref of allStacks) {\n const { stackName: refStack, region: refRegion } = ref;\n if (context.stackName && refStack === context.stackName) {\n this.logger.debug(`Skipping current stack: ${refStack}`);\n continue;\n }\n\n try {\n const lookupRegion = refRegion ?? this.resolverRegion ?? '';\n if (!lookupRegion) {\n this.logger.debug(\n `No region available for stack '${refStack}' — skipping (cdkd cannot read state without a region)`\n );\n continue;\n }\n const stateData = await context.stateBackend.getState(refStack, lookupRegion);\n if (!stateData) {\n this.logger.debug(`No state found for stack: ${refStack} (${lookupRegion})`);\n continue;\n }\n\n const { state } = stateData;\n\n if (state.outputs && exportName in state.outputs) {\n const value = state.outputs[exportName];\n this.logger.info(\n `Resolved Fn::ImportValue: ${exportName} = ${JSON.stringify(value)} (from stack: ${refStack} / ${lookupRegion})`\n );\n // Patch the index with the just-discovered entry so subsequent\n // resolves hit the O(1) path. Best-effort — index write failures\n // are logged and don't fail the resolve.\n if (context.exportIndex) {\n context.exportIndex\n .patchEntry(exportName, {\n value,\n producerStack: refStack,\n producerRegion: lookupRegion,\n })\n .catch((err) => {\n this.logger.debug(\n `Failed to patch exports index for '${exportName}': ${err instanceof Error ? err.message : String(err)}`\n );\n });\n }\n this.recordImport(context, exportName, refStack, lookupRegion);\n return value;\n }\n } catch (error) {\n this.logger.warn(\n `Failed to read state for stack ${refStack}: ${error instanceof Error ? error.message : String(error)}`\n );\n continue;\n }\n }\n\n throw new Error(\n `Fn::ImportValue: export '${exportName}' not found in any stack. ` +\n `Searched ${allStacks.length} state record(s). ` +\n `Make sure the exporting stack has been deployed and the Output has an Export.Name property.`\n );\n }\n\n /**\n * Push a resolved `Fn::ImportValue` into the consumer's recorded-imports\n * bag (when supplied by the caller). Skips duplicates within the\n * SAME bag — multiple references to the same `(exportName,\n * sourceStack, sourceRegion)` triple emit one entry.\n *\n * Concurrency: the check + push pair is purely synchronous (no\n * `await` between `some()` and `push()`), so the JS event loop\n * cannot interleave a competing `recordImport` call between the\n * dedup check and the append. The bag's lifetime is per-deploy\n * (DeployEngine resets `this.recordedImports = []` at the top of\n * each `deploy()` call), so the bag identity already serves as\n * the dedup scope.\n *\n * Cross-context dedup: when callers share the same bag instance\n * across multiple ResolverContext objects (the typical pattern —\n * DeployEngine passes `this.recordedImports` into every resolver\n * context it constructs), the dedup naturally extends across\n * contexts because the `some()` reads the shared bag. Stashing\n * the dedup Set on `context.recordedImports` directly via a\n * property would break under `verbatimModuleSyntax`-style strict\n * typing; the array scan stays O(N) where N is the per-deploy\n * import count (typically < 20), which is fine.\n */\n private recordImport(\n context: ResolverContext,\n exportName: string,\n producerStack: string,\n producerRegion: string\n ): void {\n if (!context.recordedImports) return;\n const dup = context.recordedImports.some(\n (e) =>\n e.exportName === exportName &&\n e.sourceStack === producerStack &&\n e.sourceRegion === producerRegion\n );\n if (dup) return;\n context.recordedImports.push({\n exportName,\n sourceStack: producerStack,\n sourceRegion: producerRegion,\n });\n }\n\n /**\n * Resolve Fn::GetStackOutput (cross-stack / cross-region output reference)\n *\n * Shape: { \"Fn::GetStackOutput\": { \"StackName\": \"...\", \"OutputName\": \"...\",\n * \"Region\": \"...\", \"RoleArn\": \"...\" } }\n *\n * Unlike Fn::ImportValue, the producer stack is named explicitly and no\n * Export is required. cdkd reads the producer's `outputs` from the\n * region-scoped state record at\n * `s3://{bucket}/cdkd/{StackName}/{Region}/state.json`. When `Region` is\n * omitted, the consumer's deploy region is used.\n *\n * RoleArn (cross-account) is intentionally rejected — cdkd uses S3 state,\n * not CloudFormation DescribeStacks, so a cross-account reference would\n * require assuming the role and reading the producer's separate state\n * bucket. That path is not yet implemented; we surface a clear error\n * instead of silently downgrading.\n */\n private async resolveGetStackOutput(arg: unknown, context: ResolverContext): Promise<unknown> {\n if (!arg || typeof arg !== 'object' || Array.isArray(arg)) {\n throw new Error(\n `Fn::GetStackOutput: argument must be an object with StackName/OutputName/Region/RoleArn, got ${\n arg === null ? 'null' : Array.isArray(arg) ? 'array' : typeof arg\n }`\n );\n }\n const args = arg as Record<string, unknown>;\n\n if (!('StackName' in args)) {\n throw new Error('Fn::GetStackOutput: StackName is required');\n }\n if (!('OutputName' in args)) {\n throw new Error('Fn::GetStackOutput: OutputName is required');\n }\n\n const stackName = await this.resolveValue(args['StackName'], context);\n if (typeof stackName !== 'string' || stackName === '') {\n throw new Error(\n `Fn::GetStackOutput: StackName must resolve to a non-empty string, got ${typeof stackName}`\n );\n }\n\n const outputName = await this.resolveValue(args['OutputName'], context);\n if (typeof outputName !== 'string' || outputName === '') {\n throw new Error(\n `Fn::GetStackOutput: OutputName must resolve to a non-empty string, got ${typeof outputName}`\n );\n }\n\n let region = this.resolverRegion;\n if ('Region' in args && args['Region'] !== undefined && args['Region'] !== null) {\n const resolvedRegion = await this.resolveValue(args['Region'], context);\n if (typeof resolvedRegion !== 'string' || resolvedRegion === '') {\n throw new Error(\n `Fn::GetStackOutput: Region must resolve to a non-empty string, got ${typeof resolvedRegion}`\n );\n }\n region = resolvedRegion;\n }\n\n let roleArn: string | undefined;\n if ('RoleArn' in args && args['RoleArn'] !== undefined && args['RoleArn'] !== null) {\n const resolvedRoleArn = await this.resolveValue(args['RoleArn'], context);\n if (typeof resolvedRoleArn !== 'string' || resolvedRoleArn === '') {\n throw new Error(\n `Fn::GetStackOutput: RoleArn must resolve to a non-empty string, got ${typeof resolvedRoleArn}`\n );\n }\n roleArn = resolvedRoleArn;\n }\n\n if (roleArn) {\n throw new Error(\n `Fn::GetStackOutput: cross-account references via RoleArn are not yet supported by cdkd ` +\n `(StackName=${stackName}, Region=${region}, RoleArn=${roleArn}). ` +\n `cdkd reads outputs from S3 state instead of CloudFormation DescribeStacks, ` +\n `so cross-account requires assuming the role and reading the producer account's ` +\n `state bucket — not yet implemented.`\n );\n }\n\n if (!context.stateBackend) {\n throw new Error('Fn::GetStackOutput: state backend is required for cross-stack references');\n }\n\n // Reject obvious self-reference (same stack AND same region).\n if (context.stackName && context.stackName === stackName && region === this.resolverRegion) {\n throw new Error(\n `Fn::GetStackOutput: cannot reference own stack '${stackName}' in the same region '${region}'`\n );\n }\n\n this.logger.debug(\n `Resolving Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName}`\n );\n\n const stateData = await context.stateBackend.getState(stackName, region);\n if (!stateData) {\n throw new Error(\n `Fn::GetStackOutput: stack '${stackName}' not found in region '${region}'. ` +\n `Make sure the producer stack has been deployed via cdkd.`\n );\n }\n\n const outputs = stateData.state.outputs ?? {};\n if (!(outputName in outputs)) {\n const available = Object.keys(outputs).join(', ') || '(none)';\n throw new Error(\n `Fn::GetStackOutput: output '${outputName}' not found in stack '${stackName}' (${region}). ` +\n `Available outputs: ${available}`\n );\n }\n\n const value = outputs[outputName];\n this.logger.info(\n `Resolved Fn::GetStackOutput: StackName=${stackName}, Region=${region}, OutputName=${outputName} -> ${JSON.stringify(\n value\n )}`\n );\n return value;\n }\n\n /**\n * Resolve Fn::FindInMap intrinsic function\n *\n * Fn::FindInMap: [MapName, TopLevelKey, SecondLevelKey]\n * Looks up a value in the Mappings section of the template\n */\n private async resolveFindInMap(\n findInMapArgs: [unknown, unknown, unknown],\n context: ResolverContext\n ): Promise<unknown> {\n const [rawMapName, rawTopLevelKey, rawSecondLevelKey] = findInMapArgs;\n\n // Recursively resolve each argument (they could be Refs or other intrinsic functions)\n const mapName = String(await this.resolveValue(rawMapName, context));\n const topLevelKey = String(await this.resolveValue(rawTopLevelKey, context));\n const secondLevelKey = String(await this.resolveValue(rawSecondLevelKey, context));\n\n // Access the Mappings section of the template\n const mappings = context.template.Mappings;\n if (!mappings) {\n throw new Error(`Fn::FindInMap: no Mappings section found in template`);\n }\n\n const map = mappings[mapName] as Record<string, Record<string, unknown>> | undefined;\n if (!map) {\n throw new Error(`Fn::FindInMap: mapping '${mapName}' not found in Mappings section`);\n }\n\n const topLevel = map[topLevelKey];\n if (!topLevel || typeof topLevel !== 'object') {\n throw new Error(\n `Fn::FindInMap: top-level key '${topLevelKey}' not found in mapping '${mapName}'`\n );\n }\n\n if (!(secondLevelKey in topLevel)) {\n throw new Error(\n `Fn::FindInMap: second-level key '${secondLevelKey}' not found in mapping '${mapName}' -> '${topLevelKey}'`\n );\n }\n\n const result = topLevel[secondLevelKey];\n this.logger.debug(\n `Resolved Fn::FindInMap: ${mapName}.${topLevelKey}.${secondLevelKey} -> ${JSON.stringify(result)}`\n );\n return result;\n }\n\n /**\n * Resolve Fn::Base64 intrinsic function\n *\n * Fn::Base64: valueToEncode\n * Returns the Base64 representation of the input string\n */\n private async resolveBase64(value: unknown, context: ResolverContext): Promise<string> {\n // Recursively resolve the value first (it could be another intrinsic function)\n const resolvedValue = await this.resolveValue(value, context);\n\n if (typeof resolvedValue !== 'string') {\n throw new Error(`Fn::Base64: value must resolve to a string, got ${typeof resolvedValue}`);\n }\n\n const result = Buffer.from(resolvedValue).toString('base64');\n this.logger.debug(`Resolved Fn::Base64: ${resolvedValue} -> ${result}`);\n return result;\n }\n\n /**\n * Resolve Fn::GetAZs intrinsic function\n *\n * Fn::GetAZs: region\n * Returns a list of availability zones for the specified region.\n * If region is empty string or {\"Ref\": \"AWS::Region\"}, uses the current region.\n * Results are cached per region to avoid repeated API calls.\n */\n private async resolveGetAZs(value: unknown, context: ResolverContext): Promise<string[]> {\n // Recursively resolve the value first (it could be a Ref or other intrinsic function)\n const resolvedValue = await this.resolveValue(value, context);\n\n let region: string;\n if (typeof resolvedValue === 'string' && resolvedValue !== '') {\n region = resolvedValue;\n } else {\n // Empty string or non-string: use current region\n const accountInfo = await getAccountInfo(this.resolverRegion);\n region = accountInfo.region;\n }\n\n // Check cache\n const cached = cachedAvailabilityZones[region];\n if (cached) {\n this.logger.debug(`Resolved Fn::GetAZs from cache: ${region} -> ${JSON.stringify(cached)}`);\n return cached;\n }\n\n // Call EC2 DescribeAvailabilityZones\n const awsClients = getAwsClients();\n const ec2Client = awsClients.ec2;\n\n try {\n const response = await ec2Client.send(\n new DescribeAvailabilityZonesCommand({\n Filters: [\n {\n Name: 'region-name',\n Values: [region],\n },\n {\n Name: 'state',\n Values: ['available'],\n },\n ],\n })\n );\n\n const azNames = (response.AvailabilityZones || [])\n .map((az) => az.ZoneName)\n .filter((name): name is string => name !== undefined)\n .sort();\n\n cachedAvailabilityZones[region] = azNames;\n this.logger.debug(`Resolved Fn::GetAZs: ${region} -> ${JSON.stringify(azNames)}`);\n return azNames;\n } catch (error) {\n throw new Error(\n `Fn::GetAZs: failed to describe availability zones for region '${region}': ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Resolve pseudo parameters\n *\n * Pseudo parameters are built-in CloudFormation references like AWS::Region\n */\n private async resolvePseudoParameter(\n name: string,\n context?: ResolverContext\n ): Promise<string | symbol | undefined> {\n switch (name) {\n case 'AWS::Region': {\n const accountInfo = await getAccountInfo(this.resolverRegion);\n return accountInfo.region;\n }\n\n case 'AWS::AccountId': {\n const accountInfo = await getAccountInfo(this.resolverRegion);\n return accountInfo.accountId;\n }\n\n case 'AWS::Partition': {\n const accountInfo = await getAccountInfo(this.resolverRegion);\n return accountInfo.partition;\n }\n\n case 'AWS::StackName':\n return context?.stackName ?? 'UnknownStack';\n\n case 'AWS::StackId': {\n // cdkd doesn't use CloudFormation stacks, generate a synthetic ID\n const info = await getAccountInfo(this.resolverRegion);\n return `arn:aws:cloudformation:${info.region}:${info.accountId}:stack/${context?.stackName ?? 'UnknownStack'}/cdkd`;\n }\n\n case 'AWS::URLSuffix':\n return 'amazonaws.com';\n\n case 'AWS::NotificationARNs':\n return undefined;\n\n case 'AWS::NoValue':\n // Return special symbol to indicate property should be omitted\n return AWS_NO_VALUE;\n\n default:\n return undefined;\n }\n }\n\n /**\n * Resolve CloudFormation Dynamic References in a string value\n *\n * Supports:\n * - {{resolve:secretsmanager:SECRET_ID:SecretString:JSON_KEY:VERSION_STAGE:VERSION_ID}}\n * - {{resolve:ssm:PARAMETER_NAME}}\n *\n * Results are cached to avoid repeated API calls.\n */\n async resolveDynamicReferences(value: string): Promise<string> {\n // Match all {{resolve:...}} patterns\n const pattern = /\\{\\{resolve:([^}]+)\\}\\}/g;\n let result = value;\n let match: RegExpExecArray | null;\n\n // Collect all matches first (to avoid issues with modifying string during iteration)\n const matches: Array<{ fullMatch: string; inner: string }> = [];\n while ((match = pattern.exec(value)) !== null) {\n matches.push({ fullMatch: match[0], inner: match[1]! });\n }\n\n for (const { fullMatch, inner } of matches) {\n // Check cache first\n if (fullMatch in cachedDynamicReferences) {\n result = result.replace(fullMatch, cachedDynamicReferences[fullMatch]!);\n continue;\n }\n\n const parts = inner.split(':');\n const service = parts[0];\n\n let resolved: string;\n\n if (service === 'secretsmanager') {\n resolved = await this.resolveSecretsManagerReference(inner);\n } else if (service === 'ssm') {\n resolved = await this.resolveSSMReference(parts);\n } else {\n this.logger.warn(`Unsupported dynamic reference service: ${service}`);\n continue;\n }\n\n cachedDynamicReferences[fullMatch] = resolved;\n result = result.replace(fullMatch, resolved);\n }\n\n return result;\n }\n\n /**\n * Resolve a Secrets Manager dynamic reference\n *\n * Format: secretsmanager:SECRET_ID:SecretString:JSON_KEY:VERSION_STAGE:VERSION_ID\n * SECRET_ID can be a simple name or an ARN (arn:aws:secretsmanager:REGION:ACCOUNT:secret:NAME)\n * which contains colons, so we cannot simply split on ':'.\n * Instead, we find ':SecretString:' or ':SecretBinary:' as the delimiter.\n */\n private async resolveSecretsManagerReference(inner: string): Promise<string> {\n // inner = \"secretsmanager:SECRET_ID:SecretString:JSON_KEY:VERSION_STAGE:VERSION_ID\"\n // Remove the \"secretsmanager:\" prefix\n const afterService = inner.substring('secretsmanager:'.length);\n\n // Find :SecretString: or :SecretBinary: as the delimiter between SECRET_ID and the rest\n let secretId: string;\n let jsonKey = '';\n let versionStage = '';\n let versionId = '';\n\n const secretStringIdx = afterService.indexOf(':SecretString:');\n const secretBinaryIdx = afterService.indexOf(':SecretBinary:');\n const delimiterIdx =\n secretStringIdx >= 0 && secretBinaryIdx >= 0\n ? Math.min(secretStringIdx, secretBinaryIdx)\n : secretStringIdx >= 0\n ? secretStringIdx\n : secretBinaryIdx;\n const delimiterLen =\n delimiterIdx >= 0 && delimiterIdx === secretBinaryIdx\n ? ':SecretBinary:'.length\n : ':SecretString:'.length;\n\n if (delimiterIdx >= 0) {\n secretId = afterService.substring(0, delimiterIdx);\n // remaining = \"JSON_KEY:VERSION_STAGE:VERSION_ID\"\n const remaining = afterService.substring(delimiterIdx + delimiterLen);\n const remainingParts = remaining.split(':');\n jsonKey = remainingParts[0] || '';\n versionStage = remainingParts[1] || '';\n versionId = remainingParts[2] || '';\n } else {\n // No :SecretString: or :SecretBinary: found, treat entire afterService as SECRET_ID\n secretId = afterService;\n }\n\n // Empty strings should be treated as undefined (handles trailing :: in references)\n if (!versionStage) {\n versionStage = 'AWSCURRENT';\n }\n\n if (!secretId) {\n throw new Error('Dynamic reference: secretsmanager SECRET_ID is required');\n }\n\n this.logger.debug(\n `Resolving dynamic reference: secretsmanager:${secretId}:SecretString:${jsonKey}:${versionStage}:${versionId}`\n );\n\n const awsClients = getAwsClients();\n const client = awsClients.secretsManager;\n\n const command = new GetSecretValueCommand({\n SecretId: secretId,\n ...(versionStage && versionStage !== '' && { VersionStage: versionStage }),\n ...(versionId && versionId !== '' && { VersionId: versionId }),\n });\n\n const response = await client.send(command);\n const secretString = response.SecretString;\n\n if (!secretString) {\n throw new Error(\n `Dynamic reference: secret '${secretId}' does not contain a SecretString value`\n );\n }\n\n // If JSON_KEY is specified, parse JSON and extract the key\n if (jsonKey) {\n try {\n const parsed = JSON.parse(secretString) as Record<string, unknown>;\n const keyValue = parsed[jsonKey];\n if (keyValue === undefined) {\n throw new Error(`Dynamic reference: key '${jsonKey}' not found in secret '${secretId}'`);\n }\n return stringifyValue(keyValue);\n } catch (error) {\n if (error instanceof SyntaxError) {\n throw new Error(\n `Dynamic reference: secret '${secretId}' is not valid JSON but JSON_KEY '${jsonKey}' was specified`\n );\n }\n throw error;\n }\n }\n\n // No JSON_KEY: return full secret string\n return secretString;\n }\n\n /**\n * Resolve an SSM Parameter Store dynamic reference\n *\n * Format: ssm:PARAMETER_NAME\n * Parts[0] = 'ssm'\n * Parts[1] = PARAMETER_NAME\n */\n /**\n * Resolve Fn::Cidr intrinsic function\n *\n * Fn::Cidr returns an array of CIDR address blocks.\n * Syntax: { \"Fn::Cidr\": [ ipBlock, count, cidrBits ] }\n * - ipBlock: The user-specified CIDR address block to be split\n * - count: The number of CIDRs to generate\n * - cidrBits: The number of subnet bits for the CIDR (e.g., \"64\" for /64 in IPv6)\n */\n private async resolveCidr(\n args: [unknown, unknown, unknown],\n context: ResolverContext\n ): Promise<string[]> {\n const [rawIpBlock, rawCount, rawCidrBits] = args;\n const ipBlock = (await this.resolveValue(rawIpBlock, context)) as string;\n const count = Number(await this.resolveValue(rawCount, context));\n const cidrBits = Number(await this.resolveValue(rawCidrBits, context));\n\n if (!ipBlock || typeof ipBlock !== 'string') {\n throw new Error(\n `Fn::Cidr: ipBlock must be a string, got ${typeof ipBlock}: ${JSON.stringify(ipBlock)}`\n );\n }\n\n this.logger.debug(\n `Resolving Fn::Cidr: ipBlock=${ipBlock}, count=${count}, cidrBits=${cidrBits}`\n );\n\n const isIpv6 = ipBlock.includes(':');\n const results: string[] = [];\n\n if (isIpv6) {\n // IPv6 CIDR calculation\n // Parse the base IPv6 address and prefix\n const [baseAddr, prefixStr] = ipBlock.split('/');\n const basePrefix = parseInt(prefixStr!, 10);\n const subnetPrefix = 128 - cidrBits; // cidrBits = host bits, so subnet prefix = 128 - cidrBits\n\n // Expand IPv6 address to full form\n const expanded = this.expandIPv6(baseAddr!);\n const addrBigInt = this.ipv6ToBigInt(expanded);\n\n // Calculate subnet size\n const subnetSize = BigInt(1) << BigInt(128 - subnetPrefix);\n\n // Mask the base address to the network prefix\n const prefixMask =\n (BigInt(1) << BigInt(128)) -\n BigInt(1) -\n ((BigInt(1) << BigInt(128 - basePrefix)) - BigInt(1));\n const networkBase = addrBigInt & prefixMask;\n\n for (let i = 0; i < count; i++) {\n const subnetAddr = networkBase + subnetSize * BigInt(i);\n results.push(`${this.bigIntToIPv6(subnetAddr)}/${subnetPrefix}`);\n }\n } else {\n // IPv4 CIDR calculation\n const [baseAddr, prefixStr] = ipBlock.split('/');\n const basePrefix = parseInt(prefixStr!, 10);\n const subnetPrefix = 32 - cidrBits;\n\n const parts = baseAddr!.split('.').map(Number);\n const baseInt = ((parts[0]! << 24) | (parts[1]! << 16) | (parts[2]! << 8) | parts[3]!) >>> 0;\n const subnetSize = 1 << (32 - subnetPrefix);\n const prefixMask = (0xffffffff << (32 - basePrefix)) >>> 0;\n const networkBase = (baseInt & prefixMask) >>> 0;\n\n for (let i = 0; i < count; i++) {\n const subnetAddr = (networkBase + subnetSize * i) >>> 0;\n const a = (subnetAddr >>> 24) & 0xff;\n const b = (subnetAddr >>> 16) & 0xff;\n const c = (subnetAddr >>> 8) & 0xff;\n const d = subnetAddr & 0xff;\n results.push(`${a}.${b}.${c}.${d}/${subnetPrefix}`);\n }\n }\n\n this.logger.debug(`Fn::Cidr result: ${JSON.stringify(results)}`);\n return results;\n }\n\n /** Expand IPv6 address to full 8-group form */\n private expandIPv6(addr: string): string {\n // Handle :: expansion\n if (addr.includes('::')) {\n const [left, right] = addr.split('::');\n const leftParts = left ? left.split(':') : [];\n const rightParts = right ? right.split(':') : [];\n const missing = 8 - leftParts.length - rightParts.length;\n const middle = Array.from({ length: missing }, () => '0000');\n const all = [...leftParts, ...middle, ...rightParts];\n return all.map((p: string) => p.padStart(4, '0')).join(':');\n }\n return addr\n .split(':')\n .map((p) => p.padStart(4, '0'))\n .join(':');\n }\n\n /** Convert expanded IPv6 string to BigInt */\n private ipv6ToBigInt(expanded: string): bigint {\n const parts = expanded.split(':');\n let result = BigInt(0);\n for (const part of parts) {\n result = (result << BigInt(16)) | BigInt(parseInt(part, 16));\n }\n return result;\n }\n\n /** Convert BigInt to compressed IPv6 string */\n private bigIntToIPv6(n: bigint): string {\n const parts: string[] = [];\n for (let i = 7; i >= 0; i--) {\n parts.push(((n >> BigInt(i * 16)) & BigInt(0xffff)).toString(16));\n }\n // Simple format — don't compress with :: for clarity\n return parts.join(':');\n }\n\n private async resolveSSMReference(parts: string[]): Promise<string> {\n const parameterName = parts.slice(1).join(':');\n\n if (!parameterName) {\n throw new Error('Dynamic reference: ssm PARAMETER_NAME is required');\n }\n\n this.logger.debug(`Resolving dynamic reference: ssm:${parameterName}`);\n\n const awsClients = getAwsClients();\n const client = awsClients.ssm;\n\n const command = new GetParameterCommand({\n Name: parameterName,\n WithDecryption: true,\n });\n\n const response = await client.send(command);\n const paramValue = response.Parameter?.Value;\n\n if (paramValue === undefined || paramValue === null) {\n throw new Error(\n `Dynamic reference: SSM parameter '${parameterName}' not found or has no value`\n );\n }\n\n return paramValue;\n }\n}\n","/**\n * JSON Patch Generator for Cloud Control API\n *\n * Generates RFC 6902 compliant JSON Patch documents by comparing\n * previous and desired resource properties.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc6902\n */\n\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * JSON Patch operation types\n */\nexport type PatchOperation = 'add' | 'remove' | 'replace' | 'test';\n\n/**\n * JSON Patch operation\n */\nexport interface JsonPatchOp {\n op: PatchOperation;\n path: string;\n value?: unknown;\n}\n\n/**\n * JSON Patch Generator\n *\n * Creates minimal patch documents for Cloud Control API updates.\n */\nexport class JsonPatchGenerator {\n private logger = getLogger().child('JsonPatchGenerator');\n\n /**\n * Generate JSON Patch from property differences\n *\n * @param previousProperties - Previous resource properties\n * @param desiredProperties - Desired resource properties\n * @returns Array of JSON Patch operations\n */\n generatePatch(\n previousProperties: Record<string, unknown>,\n desiredProperties: Record<string, unknown>\n ): JsonPatchOp[] {\n const patches: JsonPatchOp[] = [];\n\n // Find added or changed properties\n for (const [key, value] of Object.entries(desiredProperties)) {\n const previousValue = previousProperties[key];\n\n if (previousValue === undefined) {\n // Property added\n patches.push({\n op: 'add',\n path: `/${this.escapeJsonPointer(key)}`,\n value,\n });\n } else if (!this.deepEqual(previousValue, value)) {\n // Property changed\n patches.push({\n op: 'replace',\n path: `/${this.escapeJsonPointer(key)}`,\n value,\n });\n }\n // else: no change, skip\n }\n\n // Find removed properties\n for (const key of Object.keys(previousProperties)) {\n if (!(key in desiredProperties)) {\n patches.push({\n op: 'remove',\n path: `/${this.escapeJsonPointer(key)}`,\n });\n }\n }\n\n this.logger.debug(`Generated ${patches.length} patch operations`);\n\n return patches;\n }\n\n /**\n * Generate a full replacement patch\n *\n * This is used as a fallback when property-level patching is not feasible.\n *\n * @param properties - Desired resource properties\n * @returns Single replace operation at root\n */\n generateFullReplacementPatch(properties: Record<string, unknown>): JsonPatchOp[] {\n return [\n {\n op: 'replace',\n path: '/',\n value: properties,\n },\n ];\n }\n\n /**\n * Escape JSON Pointer special characters\n *\n * Per RFC 6901, '~' and '/' must be escaped in JSON Pointer paths.\n *\n * @see https://datatracker.ietf.org/doc/html/rfc6901\n */\n private escapeJsonPointer(str: string): string {\n return str.replace(/~/g, '~0').replace(/\\//g, '~1');\n }\n\n /**\n * Deep equality check for values\n *\n * Handles objects, arrays, primitives, null, and undefined.\n */\n private deepEqual(a: unknown, b: unknown): boolean {\n // Same reference or both null/undefined\n if (a === b) return true;\n\n // Different types\n if (typeof a !== typeof b) return false;\n\n // null comparison\n if (a === null || b === null) return false;\n\n // Array comparison\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, index) => this.deepEqual(item, b[index]));\n }\n\n // Object comparison\n if (typeof a === 'object' && typeof b === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n\n if (aKeys.length !== bKeys.length) return false;\n\n return aKeys.every((key) => this.deepEqual(aObj[key], bObj[key]));\n }\n\n // Primitive comparison (already handled by a === b above, but for clarity)\n return false;\n }\n}\n","import { ProvisioningError } from '../utils/error-handler.js';\n\n/**\n * Context passed to provider delete operations.\n *\n * `expectedRegion` is the region that the resource is expected to live in,\n * sourced from the stack state (`StackState.region`). When set, providers\n * use it to verify that a `NotFound` error from the AWS client is genuinely\n * \"the resource is gone\", and not a silent miss caused by the client\n * pointing at a different region than where the resource actually lives.\n */\nexport interface DeleteContext {\n /**\n * Region recorded in the stack state when the resource was created.\n * Optional: when omitted (or set to undefined), providers preserve their\n * existing idempotent behavior — i.e., NotFound is treated as success\n * without verification. The explicit `undefined` is permitted so callers\n * can spread `state.region` (which is itself `string | undefined`)\n * directly without first narrowing.\n */\n expectedRegion?: string | undefined;\n\n /**\n * If true, providers MUST flip per-resource deletion protection off\n * in-place before issuing the actual delete API call. Set by `cdkd\n * destroy --remove-protection` / `cdkd state destroy --remove-protection`.\n *\n * Providers handle the in-place flip-off only for protection-bearing\n * resource types (e.g. `AWS::Logs::LogGroup` `DeletionProtectionEnabled`,\n * `AWS::RDS::DBInstance` / `DBCluster` `DeletionProtection`,\n * `AWS::DocDB::DBCluster` `DeletionProtection` (DocDB DBInstance has\n * no protection field), `AWS::Neptune::DBCluster` /\n * `AWS::Neptune::DBInstance` `DeletionProtection`,\n * `AWS::DynamoDB::Table` `DeletionProtectionEnabled`,\n * `AWS::EC2::Instance` `DisableApiTermination`,\n * `AWS::ElasticLoadBalancingV2::LoadBalancer`\n * `deletion_protection.enabled` attribute). Resource types that do not\n * have a corresponding protection field treat this flag as a no-op —\n * the existing delete logic runs unchanged.\n *\n * The flip-off call is idempotent: it is always issued when this flag\n * is set (and protection is supported on the type), regardless of\n * whether the resource actually has protection enabled. AWS APIs\n * accept the no-op (already-disabled) case without error; \"not found\"\n * / similar errors during the flip-off are logged at debug and the\n * delete proceeds.\n *\n * When `false` (the default), providers behave exactly as before —\n * deletion protection blocks the destroy with whatever error AWS\n * returns (`OperationNotPermitted` / `InvalidParameterCombination` /\n * etc.) so the user must opt into the bypass explicitly.\n *\n * Note: prior to this flag, the RDS DBInstance / DBCluster providers\n * unconditionally issued a `ModifyDB{Instance,Cluster}` to clear\n * `DeletionProtection: false` before every destroy. That implicit\n * behavior is now gated on `removeProtection === true` to match the\n * other provider types — destroying an RDS resource whose deletion\n * protection was set externally (console, AWS CLI) without\n * `--remove-protection` will surface AWS's `InvalidParameterCombination`\n * error rather than silently succeed.\n */\n removeProtection?: boolean;\n}\n\n/**\n * Verify that the AWS client's region matches the region the resource is\n * expected to live in before treating a `NotFound` error as idempotent\n * delete success.\n *\n * Why: a destroy run with the wrong region would otherwise receive\n * `*NotFound` for every resource and silently strip them all from state,\n * leaving the actual AWS resources orphaned in the real region. The\n * silent-failure incident that motivated this check was a Lambda in\n * `us-west-2` removed from state by a destroy that ran with a `us-east-1`\n * client.\n *\n * Behavior:\n * - If `expectedRegion` is unset, this is a no-op (back-compat: existing\n * idempotent semantics preserved for callers that have not been\n * threaded with state region).\n * - If `clientRegion` matches `expectedRegion`, returns silently.\n * - Otherwise throws `ProvisioningError` so the caller surfaces the\n * mismatch instead of swallowing the NotFound.\n *\n * @param clientRegion Region resolved from the AWS SDK client config\n * (typically `await client.config.region()`).\n * @param expectedRegion Region recorded in stack state, or undefined if\n * the caller has no expected region.\n * @param resourceType CloudFormation resource type, used in the error\n * message and on the thrown ProvisioningError.\n * @param logicalId Logical ID of the resource, used in the error message\n * and on the thrown ProvisioningError.\n * @param physicalId Optional physical ID, used in the error message and\n * on the thrown ProvisioningError.\n */\nexport function assertRegionMatch(\n clientRegion: string | undefined,\n expectedRegion: string | undefined,\n resourceType: string,\n logicalId: string,\n physicalId?: string\n): void {\n if (!expectedRegion) {\n // Back-compat: caller did not supply state region, preserve previous\n // idempotent behavior.\n return;\n }\n\n if (!clientRegion) {\n throw new ProvisioningError(\n `Refusing to treat NotFound as idempotent delete success for ${logicalId} ` +\n `(${resourceType}): AWS client region is unknown but stack state expects ` +\n `${expectedRegion}. The resource may exist in ${expectedRegion} and would ` +\n `be silently removed from state if this NotFound were trusted.`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n if (clientRegion !== expectedRegion) {\n throw new ProvisioningError(\n `Refusing to treat NotFound as idempotent delete success for ${logicalId} ` +\n `(${resourceType}): AWS client region ${clientRegion} does not match stack ` +\n `state region ${expectedRegion}. The resource likely still exists in ` +\n `${expectedRegion}; rerun the destroy with the correct region (e.g. ` +\n `--region ${expectedRegion}).`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n}\n","import {\n CloudControlClient,\n CreateResourceCommand,\n UpdateResourceCommand,\n DeleteResourceCommand,\n GetResourceCommand,\n GetResourceRequestStatusCommand,\n type ProgressEvent,\n} from '@aws-sdk/client-cloudcontrol';\nimport { DescribeTableCommand } from '@aws-sdk/client-dynamodb';\nimport { GetRestApiCommand } from '@aws-sdk/client-api-gateway';\nimport { GetCloudFrontOriginAccessIdentityCommand } from '@aws-sdk/client-cloudfront';\nimport { GetFunctionUrlConfigCommand } from '@aws-sdk/client-lambda';\nimport { getAccountInfo } from '../deployment/intrinsic-function-resolver.js';\nimport { getAwsClients } from '../utils/aws-clients.js';\nimport { getLogger } from '../utils/logger.js';\nimport { ProvisioningError } from '../utils/error-handler.js';\nimport { JsonPatchGenerator } from './json-patch-generator.js';\nimport { assertRegionMatch, type DeleteContext } from './region-check.js';\nimport type {\n ResourceProvider,\n ResourceCreateResult,\n ResourceUpdateResult,\n ResourceImportInput,\n ResourceImportResult,\n} from '../types/resource.js';\n\n/**\n * AWS Cloud Control API Provider\n *\n * Provisions resources using the Cloud Control API, which provides\n * a unified interface for managing AWS resources.\n *\n * Note: Not all AWS resources are supported by Cloud Control API.\n * Use isSupportedResourceType() to check before usage.\n */\n/**\n * Properties that CC API expects as JSON strings, not objects.\n * CC API schema declares these as type: [\"string\", \"object\"] but\n * the implementation only accepts strings.\n */\nconst JSON_STRING_PROPERTIES: Record<string, Set<string>> = {\n 'AWS::Events::Rule': new Set(['EventPattern']),\n};\n\n/**\n * Stringify object properties that CC API expects as JSON strings.\n */\nfunction stringifyJsonProperties(\n resourceType: string,\n properties: Record<string, unknown>\n): Record<string, unknown> {\n const jsonProps = JSON_STRING_PROPERTIES[resourceType];\n if (!jsonProps) return properties;\n\n const result = { ...properties };\n for (const key of jsonProps) {\n if (key in result && typeof result[key] === 'object' && result[key] !== null) {\n result[key] = JSON.stringify(result[key]);\n }\n }\n return result;\n}\n\n/**\n * Recursively strip null and undefined values from an object.\n * This prevents CC API errors caused by null property values\n * (e.g., EventBridge Rule with null ScheduleExpression causes Java NPE).\n */\nfunction stripNullValues(obj: unknown): unknown {\n if (obj === null || obj === undefined) {\n return undefined;\n }\n if (Array.isArray(obj)) {\n return obj.map(stripNullValues).filter((v) => v !== undefined);\n }\n if (typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n const stripped = stripNullValues(value);\n if (stripped !== undefined) {\n result[key] = stripped;\n }\n }\n return result;\n }\n return obj;\n}\n\nexport class CloudControlProvider implements ResourceProvider {\n private cloudControlClient: CloudControlClient;\n private logger = getLogger().child('CloudControlProvider');\n private patchGenerator = new JsonPatchGenerator();\n\n // Maximum time to wait for operation completion (15 minutes)\n private readonly MAX_WAIT_TIME_MS = 15 * 60 * 1000;\n // Initial poll interval (1 second) - increases with 1.5x exponential backoff\n private readonly INITIAL_POLL_INTERVAL_MS = 1_000;\n // Maximum poll interval (10 seconds)\n private readonly MAX_POLL_INTERVAL_MS = 10_000;\n\n constructor() {\n const awsClients = getAwsClients();\n this.cloudControlClient = awsClients.cloudControl;\n }\n\n /**\n * Create a resource using Cloud Control API\n */\n async create(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n ): Promise<ResourceCreateResult> {\n this.logger.debug(`Creating resource ${logicalId} (${resourceType})`);\n\n try {\n // Start resource creation\n const cleanProperties = stripNullValues(properties) as Record<string, unknown>;\n const ccProperties = stringifyJsonProperties(resourceType, cleanProperties);\n const desiredState = JSON.stringify(ccProperties);\n this.logger.debug(`DesiredState for ${logicalId}: ${desiredState}`);\n const createResponse = await this.cloudControlClient.send(\n new CreateResourceCommand({\n TypeName: resourceType,\n DesiredState: desiredState,\n })\n );\n\n if (!createResponse.ProgressEvent?.RequestToken) {\n throw new ProvisioningError(\n `Failed to create resource ${logicalId}: No request token received`,\n resourceType,\n logicalId\n );\n }\n\n this.logger.debug(\n `Create request submitted for ${logicalId}, token: ${createResponse.ProgressEvent.RequestToken}`\n );\n\n // Wait for creation to complete\n const progressEvent = await this.waitForOperation(\n createResponse.ProgressEvent.RequestToken,\n logicalId,\n 'CREATE'\n );\n\n if (!progressEvent.Identifier) {\n throw new ProvisioningError(\n `Failed to create resource ${logicalId}: No physical ID returned`,\n resourceType,\n logicalId\n );\n }\n\n this.logger.debug(`Created resource ${logicalId}, physical ID: ${progressEvent.Identifier}`);\n\n // Parse resource properties to extract attributes\n const result: ResourceCreateResult = {\n physicalId: progressEvent.Identifier,\n };\n\n if (progressEvent.ResourceModel) {\n result.attributes = this.parseResourceModel(progressEvent.ResourceModel);\n }\n\n // Enrich attributes with computed values for specific resource types\n result.attributes = await this.enrichResourceAttributes(\n resourceType,\n progressEvent.Identifier,\n result.attributes || {}\n );\n\n return result;\n } catch (error) {\n this.handleError(error, 'CREATE', resourceType, logicalId);\n }\n }\n\n /**\n * Update a resource using Cloud Control API\n */\n async update(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n previousProperties: Record<string, unknown>\n ): Promise<ResourceUpdateResult> {\n this.logger.debug(\n `Updating resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`\n );\n\n try {\n // Strip null/undefined values and stringify JSON properties before generating patch\n const cleanPreviousProperties = stringifyJsonProperties(\n resourceType,\n stripNullValues(previousProperties) as Record<string, unknown>\n );\n const cleanProperties = stringifyJsonProperties(\n resourceType,\n stripNullValues(properties) as Record<string, unknown>\n );\n\n // Generate JSON Patch document\n const patch = this.patchGenerator.generatePatch(cleanPreviousProperties, cleanProperties);\n\n if (patch.length === 0) {\n // No changes detected\n this.logger.debug(`No property changes detected for ${logicalId}, skipping update`);\n return {\n physicalId,\n wasReplaced: false,\n };\n }\n\n this.logger.debug(\n `Generated ${patch.length} patch operations for ${logicalId}: ${JSON.stringify(patch)}`\n );\n\n // Start resource update\n const updateResponse = await this.cloudControlClient.send(\n new UpdateResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n PatchDocument: JSON.stringify(patch),\n })\n );\n\n if (!updateResponse.ProgressEvent?.RequestToken) {\n throw new ProvisioningError(\n `Failed to update resource ${logicalId}: No request token received`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n this.logger.debug(\n `Update request submitted for ${logicalId}, token: ${updateResponse.ProgressEvent.RequestToken}`\n );\n\n // Wait for update to complete\n const progressEvent = await this.waitForOperation(\n updateResponse.ProgressEvent.RequestToken,\n logicalId,\n 'UPDATE'\n );\n\n this.logger.debug(`Updated resource ${logicalId}`);\n\n // Parse resource properties to extract attributes\n // Resource replacement for immutable property changes is detected and handled\n // by DeployEngine (immutable property detection + CREATE→DELETE flow) before\n // reaching this update method, so wasReplaced is always false here.\n const result: ResourceUpdateResult = {\n physicalId,\n wasReplaced: false,\n };\n\n if (progressEvent.ResourceModel) {\n result.attributes = this.parseResourceModel(progressEvent.ResourceModel);\n }\n\n // Enrich attributes with computed values for specific resource types\n result.attributes = await this.enrichResourceAttributes(\n resourceType,\n physicalId,\n result.attributes || {}\n );\n\n return result;\n } catch (error) {\n this.handleError(error, 'UPDATE', resourceType, logicalId, physicalId);\n }\n }\n\n /**\n * Delete a resource using Cloud Control API\n */\n async delete(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n _properties?: Record<string, unknown>,\n context?: DeleteContext\n ): Promise<void> {\n this.logger.debug(\n `Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`\n );\n\n try {\n // Start resource deletion\n const deleteResponse = await this.cloudControlClient.send(\n new DeleteResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n })\n );\n\n if (!deleteResponse.ProgressEvent?.RequestToken) {\n throw new ProvisioningError(\n `Failed to delete resource ${logicalId}: No request token received`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n this.logger.debug(\n `Delete request submitted for ${logicalId}, token: ${deleteResponse.ProgressEvent.RequestToken}`\n );\n\n // Wait for deletion to complete\n await this.waitForOperation(deleteResponse.ProgressEvent.RequestToken, logicalId, 'DELETE');\n\n this.logger.debug(`Deleted resource ${logicalId}`);\n } catch (error) {\n // Treat \"not found\" / \"does not exist\" as idempotent success for DELETE,\n // but only when the AWS client is operating against the same region the\n // resource was deployed to. A region mismatch must surface — otherwise a\n // destroy run with the wrong region would silently strip every resource\n // from state while leaving the actual AWS resources orphaned.\n const err = error as { name?: string; message?: string };\n if (\n err.name === 'ResourceNotFoundException' ||\n err.message?.includes('does not exist') ||\n err.message?.includes('not found') ||\n err.message?.includes('NotFound')\n ) {\n const clientRegion = await this.cloudControlClient.config.region();\n assertRegionMatch(\n clientRegion,\n context?.expectedRegion,\n resourceType,\n logicalId,\n physicalId\n );\n this.logger.debug(`Resource ${logicalId} already deleted (not found), treating as success`);\n return;\n }\n this.handleError(error, 'DELETE', resourceType, logicalId, physicalId);\n }\n }\n\n /**\n * Get current state of a resource\n */\n async getResourceState(\n resourceType: string,\n physicalId: string\n ): Promise<Record<string, unknown> | null> {\n try {\n const response = await this.cloudControlClient.send(\n new GetResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n })\n );\n\n if (!response.ResourceDescription?.Properties) {\n return null;\n }\n\n return this.parseResourceModel(response.ResourceDescription.Properties);\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return null;\n }\n throw error;\n }\n }\n\n /**\n * Wait for an asynchronous operation to complete\n */\n private async waitForOperation(\n requestToken: string,\n logicalId: string,\n operation: 'CREATE' | 'UPDATE' | 'DELETE'\n ): Promise<ProgressEvent> {\n const startTime = Date.now();\n let attempts = 0;\n let pollInterval = this.INITIAL_POLL_INTERVAL_MS;\n\n while (Date.now() - startTime < this.MAX_WAIT_TIME_MS) {\n attempts++;\n\n const statusResponse = await this.cloudControlClient.send(\n new GetResourceRequestStatusCommand({\n RequestToken: requestToken,\n })\n );\n\n const progressEvent = statusResponse.ProgressEvent;\n\n if (!progressEvent) {\n throw new ProvisioningError(\n `Failed to get status for ${logicalId}: No progress event`,\n 'Unknown',\n logicalId\n );\n }\n\n this.logger.debug(\n `${operation} ${logicalId}: ${progressEvent.OperationStatus} (attempt ${attempts}, next poll ${pollInterval}ms)`\n );\n\n switch (progressEvent.OperationStatus) {\n case 'SUCCESS':\n return progressEvent;\n\n case 'FAILED':\n throw new ProvisioningError(\n `${operation} failed for ${logicalId}: ${progressEvent.StatusMessage || 'Unknown error'}`,\n progressEvent.TypeName || 'Unknown',\n logicalId,\n progressEvent.Identifier\n );\n\n case 'CANCEL_COMPLETE':\n throw new ProvisioningError(\n `${operation} cancelled for ${logicalId}`,\n progressEvent.TypeName || 'Unknown',\n logicalId,\n progressEvent.Identifier\n );\n\n case 'IN_PROGRESS':\n case 'PENDING':\n // Exponential backoff with 1.5x multiplier for flatter curve:\n // 1s → 1.5s → 2.25s → 3.4s → 5s → 7.5s → 10s (capped)\n // Most CC API operations complete in 1-5s, so slower ramp-up\n // polls more frequently during the common case.\n await this.sleep(pollInterval);\n pollInterval = Math.min(Math.ceil(pollInterval * 1.5), this.MAX_POLL_INTERVAL_MS);\n break;\n\n default:\n this.logger.warn(\n `Unknown operation status for ${logicalId}: ${progressEvent.OperationStatus}`\n );\n await this.sleep(pollInterval);\n pollInterval = Math.min(Math.ceil(pollInterval * 1.5), this.MAX_POLL_INTERVAL_MS);\n }\n }\n\n throw new ProvisioningError(\n `${operation} timeout for ${logicalId} after ${this.MAX_WAIT_TIME_MS / 1000}s`,\n 'Unknown',\n logicalId\n );\n }\n\n /**\n * Parse resource model JSON string\n */\n private parseResourceModel(resourceModel: string): Record<string, unknown> {\n try {\n return JSON.parse(resourceModel) as Record<string, unknown>;\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.logger.warn(\n `Failed to parse resource model: ${errorMessage}\\n` +\n `Raw model: ${resourceModel.substring(0, 500)}${resourceModel.length > 500 ? '...' : ''}`\n );\n return {};\n }\n }\n\n /**\n * Enrich resource attributes with computed values\n *\n * CC API GetResource returns property names that match CloudFormation\n * Fn::GetAtt attribute names, so all properties are passed through as-is.\n * This method adds fallback attributes for edge cases where CC API\n * may not return certain values.\n */\n private async enrichResourceAttributes(\n resourceType: string,\n physicalId: string,\n attributes: Record<string, unknown>\n ): Promise<Record<string, unknown>> {\n const enriched: Record<string, unknown> = { ...attributes };\n\n // Fallback: compute attributes that CC API may not return\n switch (resourceType) {\n case 'AWS::S3::Bucket':\n // S3 bucket ARN: arn:aws:s3:::bucket-name\n if (!enriched['Arn']) {\n enriched['Arn'] = `arn:aws:s3:::${physicalId}`;\n }\n break;\n\n case 'AWS::DynamoDB::Table':\n // Fallback: CC API GetResource may not include StreamArn when streams are enabled.\n // Call DescribeTable to retrieve LatestStreamArn if not already present.\n if (!enriched['StreamArn']) {\n try {\n const dynamoDBClient = getAwsClients().dynamoDB;\n const describeResponse = await dynamoDBClient.send(\n new DescribeTableCommand({ TableName: physicalId })\n );\n const latestStreamArn = describeResponse.Table?.LatestStreamArn;\n if (latestStreamArn) {\n enriched['StreamArn'] = latestStreamArn;\n this.logger.debug(\n `Enriched DynamoDB StreamArn for ${physicalId}: ${latestStreamArn}`\n );\n }\n } catch (error) {\n // Best-effort: don't fail the operation if DescribeTable fails\n this.logger.debug(\n `Failed to get DynamoDB StreamArn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n case 'AWS::ApiGateway::RestApi':\n // Fallback: ensure RootResourceId is present.\n // CC API GetResource typically returns it, but retrieve via SDK if missing.\n if (!enriched['RootResourceId']) {\n try {\n const apiGatewayClient = getAwsClients().apiGateway;\n const getRestApiResponse = await apiGatewayClient.send(\n new GetRestApiCommand({ restApiId: physicalId })\n );\n if (getRestApiResponse.rootResourceId) {\n enriched['RootResourceId'] = getRestApiResponse.rootResourceId;\n this.logger.debug(\n `Enriched RestApi RootResourceId for ${physicalId}: ${getRestApiResponse.rootResourceId}`\n );\n }\n } catch (error) {\n // Best-effort: don't fail the operation if GetRestApi fails\n this.logger.debug(\n `Failed to get RestApi RootResourceId for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n // Ensure RestApiId is set (physical ID is the rest-api-id)\n if (!enriched['RestApiId']) {\n enriched['RestApiId'] = physicalId;\n }\n break;\n\n case 'AWS::CloudFront::CloudFrontOriginAccessIdentity':\n // Fallback: ensure S3CanonicalUserId is present.\n // CC API GetResource typically returns it, but retrieve via SDK if missing.\n if (!enriched['S3CanonicalUserId']) {\n try {\n const cloudFrontClient = getAwsClients().cloudFront;\n const oaiResponse = await cloudFrontClient.send(\n new GetCloudFrontOriginAccessIdentityCommand({ Id: physicalId })\n );\n const s3CanonicalUserId = oaiResponse.CloudFrontOriginAccessIdentity?.S3CanonicalUserId;\n if (s3CanonicalUserId) {\n enriched['S3CanonicalUserId'] = s3CanonicalUserId;\n this.logger.debug(\n `Enriched CloudFront OAI S3CanonicalUserId for ${physicalId}: ${s3CanonicalUserId}`\n );\n }\n } catch (error) {\n // Best-effort: don't fail the operation\n this.logger.debug(\n `Failed to get CloudFront OAI S3CanonicalUserId for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n case 'AWS::KMS::Key':\n // CC API may not return Arn in ResourceModel.\n // Physical ID is the KeyId (UUID), so construct the ARN.\n if (!enriched['Arn']) {\n try {\n const kmsAccountInfo = await getAccountInfo();\n enriched['Arn'] =\n `arn:${kmsAccountInfo.partition}:kms:${kmsAccountInfo.region}:${kmsAccountInfo.accountId}:key/${physicalId}`;\n this.logger.debug(`Enriched KMS Key Arn for ${physicalId}: ${String(enriched['Arn'])}`);\n } catch (error) {\n this.logger.debug(\n `Failed to construct KMS Key Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n if (!enriched['KeyId']) {\n enriched['KeyId'] = physicalId;\n }\n break;\n\n case 'AWS::CloudFront::OriginAccessControl':\n // CC API physicalId is the OAC ID\n if (!enriched['Id']) enriched['Id'] = physicalId;\n break;\n\n case 'AWS::Route53::HealthCheck':\n // CC API physicalId is the HealthCheck ID\n if (!enriched['HealthCheckId']) enriched['HealthCheckId'] = physicalId;\n break;\n\n case 'AWS::ECR::Repository':\n // CC API physicalId is the repository name, construct ARN\n if (!enriched['Arn']) {\n try {\n const ecrAccountInfo = await getAccountInfo();\n enriched['Arn'] =\n `arn:${ecrAccountInfo.partition}:ecr:${ecrAccountInfo.region}:${ecrAccountInfo.accountId}:repository/${physicalId}`;\n this.logger.debug(\n `Enriched ECR Repository Arn for ${physicalId}: ${String(enriched['Arn'])}`\n );\n } catch (error) {\n this.logger.debug(\n `Failed to construct ECR Repository Arn: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n if (!enriched['RepositoryUri']) {\n try {\n const ecrAccountInfo = await getAccountInfo();\n enriched['RepositoryUri'] =\n `${ecrAccountInfo.accountId}.dkr.ecr.${ecrAccountInfo.region}.amazonaws.com/${physicalId}`;\n } catch {\n /* best effort */\n }\n }\n break;\n\n case 'AWS::EC2::EIP':\n // CC API returns composite physicalId: \"PublicIp|AllocationId\"\n // Extract individual attributes for Fn::GetAtt resolution\n if (physicalId.includes('|')) {\n const [publicIp, allocationId] = physicalId.split('|');\n if (!enriched['AllocationId']) enriched['AllocationId'] = allocationId;\n if (!enriched['PublicIp']) enriched['PublicIp'] = publicIp;\n this.logger.debug(\n `Enriched EIP attributes: AllocationId=${allocationId}, PublicIp=${publicIp}`\n );\n }\n break;\n\n case 'AWS::Lambda::Version':\n // CC API physicalId for Lambda Version is the full version ARN\n // (e.g., arn:aws:lambda:us-east-1:123456:function:MyFunc:1).\n // Lambda::Alias FunctionVersion property needs just the version number.\n if (!enriched['Version']) {\n const versionSegments = physicalId.split(':');\n const versionNumber = versionSegments[versionSegments.length - 1];\n enriched['Version'] = versionNumber;\n this.logger.debug(`Enriched Lambda Version for ${physicalId}: ${versionNumber}`);\n }\n break;\n\n case 'AWS::Kinesis::Stream':\n // CC API physicalId for Kinesis Stream is the stream name, not the ARN.\n // Fn::GetAtt [Stream, Arn] needs the full ARN.\n if (!enriched['Arn']) {\n try {\n const kinesisAccountInfo = await getAccountInfo();\n enriched['Arn'] =\n `arn:${kinesisAccountInfo.partition}:kinesis:${kinesisAccountInfo.region}:${kinesisAccountInfo.accountId}:stream/${physicalId}`;\n this.logger.debug(\n `Enriched Kinesis Stream Arn for ${physicalId}: ${String(enriched['Arn'])}`\n );\n } catch (error) {\n this.logger.debug(\n `Failed to construct Kinesis Stream Arn for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n case 'AWS::Lambda::Url':\n // CC API CREATE response may not include FunctionUrl in ResourceModel.\n // Use Lambda SDK to retrieve it for Fn::GetAtt resolution.\n if (!enriched['FunctionUrl']) {\n try {\n const lambdaClient = getAwsClients().lambda;\n // physicalId is the FunctionArn for Lambda URL\n const urlConfig = await lambdaClient.send(\n new GetFunctionUrlConfigCommand({ FunctionName: physicalId })\n );\n if (urlConfig.FunctionUrl) {\n enriched['FunctionUrl'] = urlConfig.FunctionUrl;\n this.logger.debug(\n `Enriched Lambda URL FunctionUrl for ${physicalId}: ${urlConfig.FunctionUrl}`\n );\n }\n if (urlConfig.FunctionArn) {\n enriched['FunctionArn'] = urlConfig.FunctionArn;\n }\n } catch (error) {\n this.logger.debug(\n `Failed to get Lambda URL config for ${physicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n break;\n\n default:\n break;\n }\n\n return enriched;\n }\n\n /**\n * Handle errors and throw ProvisioningError\n */\n private handleError(\n error: unknown,\n operation: string,\n resourceType: string,\n logicalId: string,\n physicalId?: string\n ): never {\n const err = error as { name?: string; message?: string };\n\n // Check if resource type is not supported\n if (err.name === 'UnsupportedActionException' || err.name === 'TypeNotFoundException') {\n throw new ProvisioningError(\n `Resource type ${resourceType} is not supported by Cloud Control API and no SDK provider is registered.\\n` +\n `Please report this issue at https://github.com/go-to-k/cdkd/issues so we can add SDK provider support.\\n` +\n `Error: ${err.message || 'Unknown error'}`,\n resourceType,\n logicalId,\n physicalId,\n error instanceof Error ? error : undefined\n );\n }\n\n // Re-throw if already a ProvisioningError\n if (error instanceof ProvisioningError) {\n throw error;\n }\n\n // Wrap other errors\n throw new ProvisioningError(\n `${operation} failed for ${logicalId}: ${err.message || 'Unknown error'}`,\n resourceType,\n logicalId,\n physicalId,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * Sleep for specified milliseconds\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Check if a resource type is supported by Cloud Control API\n *\n * This is a best-effort check. Some resource types may still fail\n * even if they appear to be supported.\n */\n static isSupportedResourceType(resourceType: string): boolean {\n // Common resource types that are NOT supported by Cloud Control API\n const unsupportedTypes = new Set([\n // IAM (most types not supported)\n 'AWS::IAM::Role',\n 'AWS::IAM::Policy',\n 'AWS::IAM::ManagedPolicy',\n 'AWS::IAM::User',\n 'AWS::IAM::Group',\n 'AWS::IAM::InstanceProfile',\n\n // Lambda layers\n 'AWS::Lambda::LayerVersion',\n\n // S3 bucket policies (use SDK instead)\n 'AWS::S3::BucketPolicy',\n\n // CloudFormation-specific resources\n 'AWS::CloudFormation::Stack',\n 'AWS::CloudFormation::WaitCondition',\n 'AWS::CloudFormation::WaitConditionHandle',\n 'AWS::CloudFormation::CustomResource',\n\n // CDK-specific resources\n 'AWS::CDK::Metadata',\n 'Custom::CDKBucketDeployment',\n 'Custom::S3AutoDeleteObjects',\n\n // Route53 hosted zones (complex)\n 'AWS::Route53::HostedZone',\n\n // ACM certificates (validation complexity)\n 'AWS::CertificateManager::Certificate',\n ]);\n\n if (unsupportedTypes.has(resourceType)) {\n return false;\n }\n\n // Custom resources are never supported by Cloud Control\n if (\n resourceType.startsWith('Custom::') ||\n resourceType.startsWith('AWS::CloudFormation::CustomResource')\n ) {\n return false;\n }\n\n // Most other AWS:: resources should be supported\n // (This is optimistic; some may still fail)\n return resourceType.startsWith('AWS::');\n }\n\n /**\n * Read the AWS-current properties of a resource managed via Cloud Control\n * API, for `cdkd drift` comparison.\n *\n * Strategy: `GetResource(TypeName, Identifier)` returns `ResourceModel` as\n * a JSON string of every property AWS reports for the resource. Parse and\n * surface it as the AWS-current snapshot — the drift command intersects\n * this against the keys present in cdkd state, so AWS-only keys (timestamps,\n * generated ids, etc.) are filtered out at compare time.\n *\n * Returns `undefined` for the unique cases that mean \"drift unknown\" (the\n * resource was deleted out from under cdkd, or the response had no\n * Properties field). Re-throws on any other error so the drift command can\n * surface throttling / access-denied issues to the user.\n *\n * This single CC API implementation gives drift detection coverage to every\n * resource type that goes through CC API — the majority of cdkd's surface.\n * SDK Providers add their own `readCurrentState` incrementally (PR D).\n */\n async readCurrentState(\n physicalId: string,\n _logicalId: string,\n resourceType: string,\n _properties?: Record<string, unknown>\n ): Promise<Record<string, unknown> | undefined> {\n try {\n const response = await this.cloudControlClient.send(\n new GetResourceCommand({\n TypeName: resourceType,\n Identifier: physicalId,\n })\n );\n\n const raw = response.ResourceDescription?.Properties;\n if (typeof raw !== 'string' || raw.length === 0) {\n return undefined;\n }\n\n const parsed = JSON.parse(raw) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return undefined;\n }\n\n return parsed as Record<string, unknown>;\n } catch (error) {\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * Adopt an already-deployed resource into cdkd state via Cloud Control API.\n *\n * Strategy: explicit-override only.\n * - With `knownPhysicalId` (from `--resource <id>=<physicalId>` or\n * `--resource-mapping`): call `GetResource(TypeName, Identifier)`,\n * parse `ResourceModel` (returned as a JSON string by CC API), and\n * return its keys as `attributes`.\n * - Without `knownPhysicalId`: return `null`. CC API has no efficient\n * `aws:cdk:path`-tag lookup — `ListResources` returns identifiers\n * only, so tag lookup would require one `GetResource` per resource\n * in the account, plus per-service tag-API calls (which CC API\n * doesn't expose uniformly). Cost vs. value isn't worth it; users\n * who need adoption for CC-API-only resource types should pass\n * `--resource <id>=<physicalId>` for those resources.\n *\n * SDK providers (S3, Lambda, IAM Role, etc.) implement their own\n * `import` with tag-based auto-lookup; this fallback only kicks in for\n * resource types that don't have a dedicated SDK provider.\n */\n async import(input: ResourceImportInput): Promise<ResourceImportResult | null> {\n if (!input.knownPhysicalId) {\n // Explicit-override-only: no auto lookup via CC API.\n return null;\n }\n\n try {\n const resp = await this.cloudControlClient.send(\n new GetResourceCommand({\n TypeName: input.resourceType,\n Identifier: input.knownPhysicalId,\n })\n );\n\n // CC API returns `ResourceModel` as a JSON string of all the\n // resource's properties — its keys map 1:1 to GetAtt-compatible\n // attribute names. Parse and surface them so deploy-time\n // `Fn::GetAtt` resolution can find them in state.\n let attributes: Record<string, unknown> = {};\n const raw = resp.ResourceDescription?.Properties;\n if (typeof raw === 'string' && raw.length > 0) {\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n attributes = parsed as Record<string, unknown>;\n }\n } catch (parseErr) {\n this.logger.debug(\n `Failed to parse CC API ResourceModel for ${input.resourceType}/${input.knownPhysicalId}: ${\n parseErr instanceof Error ? parseErr.message : String(parseErr)\n }`\n );\n // Fall through with empty attributes — physicalId is enough\n // to register the resource in state. Fn::GetAtt will\n // reconstruct attributes via constructAttribute at deploy.\n }\n }\n\n return { physicalId: input.knownPhysicalId, attributes };\n } catch (error) {\n // ResourceNotFoundException → null (caller marks \"not found\").\n // Any other error (access denied, bad TypeName, throttling) →\n // re-throw so the caller can surface it.\n const err = error as { name?: string };\n if (err.name === 'ResourceNotFoundException') {\n return null;\n }\n throw error;\n }\n }\n}\n","import {\n LambdaClient,\n InvokeCommand,\n waitUntilFunctionActiveV2,\n waitUntilFunctionUpdatedV2,\n type InvocationResponse,\n} from '@aws-sdk/client-lambda';\nimport { SNSClient, PublishCommand } from '@aws-sdk/client-sns';\nimport {\n S3Client,\n PutObjectCommand,\n GetObjectCommand,\n DeleteObjectCommand,\n} from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\nimport { getLogger } from '../../utils/logger.js';\nimport { getAwsClients } from '../../utils/aws-clients.js';\nimport { ProvisioningError } from '../../utils/error-handler.js';\nimport { type DeleteContext } from '../region-check.js';\nimport type {\n ResourceProvider,\n ResourceCreateResult,\n ResourceUpdateResult,\n ResourceImportInput,\n ResourceImportResult,\n} from '../../types/resource.js';\n\n/**\n * CloudFormation Custom Resource Response format\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html\n */\ninterface CfnCustomResourceResponse {\n Status: 'SUCCESS' | 'FAILED';\n Reason?: string;\n PhysicalResourceId?: string;\n StackId?: string;\n RequestId?: string;\n LogicalResourceId?: string;\n NoEcho?: boolean;\n Data?: Record<string, unknown>;\n}\n\n/**\n * Custom Resource Lambda Response Payload (direct return)\n * Some handlers return data directly in the Lambda payload instead of via ResponseURL\n */\ninterface CustomResourceResponsePayload {\n PhysicalResourceId?: string;\n Data?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\n/**\n * Configuration for Custom Resource Provider\n */\nexport interface CustomResourceProviderConfig {\n /** S3 bucket name for storing custom resource responses */\n responseBucket?: string;\n /** S3 key prefix for response objects */\n responsePrefix?: string;\n /**\n * Max time (ms) to wait for async custom resource responses (e.g., CDK Provider framework\n * with isCompleteHandler that uses Step Functions polling).\n * Default: 1 hour (3600000ms), matching CDK's default totalTimeout.\n */\n asyncResponseTimeoutMs?: number;\n}\n\n/**\n * Type guard to validate Lambda response payload structure\n */\nfunction isCustomResourceResponsePayload(value: unknown): value is CustomResourceResponsePayload {\n if (typeof value !== 'object' || value === null) {\n return false;\n }\n\n const payload = value as Record<string, unknown>;\n\n if ('PhysicalResourceId' in payload && typeof payload['PhysicalResourceId'] !== 'string') {\n return false;\n }\n\n if ('Data' in payload) {\n if (typeof payload['Data'] !== 'object' || payload['Data'] === null) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Parse Lambda response payload with type safety\n */\nfunction parseLambdaPayload(payloadBytes: Uint8Array | undefined): CustomResourceResponsePayload {\n if (!payloadBytes) {\n return {};\n }\n\n const payloadString = Buffer.from(payloadBytes).toString();\n\n // Handle empty or null responses\n if (!payloadString || payloadString === 'null' || payloadString === '\"\"') {\n return {};\n }\n\n const parsed: unknown = JSON.parse(payloadString);\n\n if (!isCustomResourceResponsePayload(parsed)) {\n throw new Error(`Invalid Lambda response payload format: ${JSON.stringify(parsed)}`);\n }\n\n return parsed;\n}\n\n/**\n * Custom Resource Provider\n *\n * Implements Lambda-backed custom resources by invoking the Lambda function\n * specified in the ServiceToken property.\n *\n * This provider follows the CloudFormation custom resource protocol:\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/custom-resources.html\n *\n * Supports both standard custom resources and CDK's Provider framework:\n *\n * **Standard custom resources:**\n * - ServiceToken Lambda is invoked synchronously\n * - Handler sends cfn-response to ResponseURL (S3 pre-signed URL) or returns directly\n * - Short polling timeout (30 seconds)\n *\n * **CDK Provider framework (with isCompleteHandler):**\n * - ServiceToken points to the framework's onEvent wrapper Lambda\n * - Lambda invokes user's onEventHandler, then starts a Step Functions state machine\n * - Step Functions polls the isCompleteHandler until IsComplete: true\n * - Step Functions sends cfn-response to ResponseURL when done\n * - Lambda returns null/empty payload (async pattern detected automatically)\n * - Long polling timeout with exponential backoff (default: 1 hour)\n *\n * Response handling strategy:\n * 1. Generate a pre-signed S3 PUT URL as the ResponseURL (valid for 2 hours)\n * 2. Invoke Lambda synchronously (RequestResponse)\n * 3. Check Lambda payload for direct response (simple handlers)\n * 4. If no direct response, detect async pattern and poll S3 with appropriate timeout\n */\nexport class CustomResourceProvider implements ResourceProvider {\n private lambdaClient: LambdaClient;\n private snsClient: SNSClient;\n private s3Client: S3Client;\n private logger = getLogger().child('CustomResourceProvider');\n private responseBucket: string | undefined;\n private responsePrefix: string;\n\n /**\n * Opt out of the deploy engine's outer transient-error retry loop.\n *\n * The loop re-invokes `provider.create()` from the top on a transient\n * SDK error (IAM propagation, HTTP 429/503, etc.). Each invocation\n * generates a brand-new RequestId and a brand-new pre-signed S3\n * response URL via `prepareInvocation()`. If the underlying Lambda has\n * already started — e.g. an outer retry fired between the placeholder\n * `PutObject` and the `Invoke`, or after the `Invoke` returned but a\n * spurious downstream error fired — the first attempt's Lambda\n * response lands at an S3 key that nobody polls, hanging the deploy\n * until the polling timeout. The provider already polls with its own\n * exponential backoff for async patterns (CDK Provider framework with\n * isCompleteHandler), so an outer retry adds nothing but the multi-\n * key bug.\n */\n readonly disableOuterRetry = true;\n\n /** Max time to wait for synchronous S3 response after Lambda invocation (30 seconds) */\n private readonly SYNC_RESPONSE_TIMEOUT_MS = 30_000;\n /** Max time to wait for async S3 response (CDK Provider framework with isCompleteHandler) */\n private readonly asyncResponseTimeoutMs: number;\n /** Default async response timeout: 1 hour (matches CDK's default totalTimeout) */\n private static readonly DEFAULT_ASYNC_RESPONSE_TIMEOUT_MS = 3_600_000;\n /** Initial poll interval for checking S3 response (2 seconds) */\n private readonly INITIAL_POLL_INTERVAL_MS = 2_000;\n /** Max poll interval for async polling with exponential backoff (30 seconds) */\n private readonly MAX_POLL_INTERVAL_MS = 30_000;\n\n constructor(config?: CustomResourceProviderConfig) {\n const awsClients = getAwsClients();\n this.lambdaClient = awsClients.lambda;\n this.snsClient = awsClients.sns;\n this.s3Client = awsClients.s3;\n this.responseBucket = config?.responseBucket;\n this.responsePrefix = config?.responsePrefix ?? 'custom-resource-responses';\n this.asyncResponseTimeoutMs =\n config?.asyncResponseTimeoutMs ?? CustomResourceProvider.DEFAULT_ASYNC_RESPONSE_TIMEOUT_MS;\n }\n\n /**\n * Self-reported minimum per-resource timeout.\n *\n * Custom Resource async invocations (CDK Provider framework with\n * `isCompleteHandler`) poll for up to `asyncResponseTimeoutMs`\n * (default 1 hour, matching CDK's `totalTimeout` default). The deploy\n * engine's global `--resource-timeout` default is 30 minutes, which\n * would abort a perfectly healthy CR mid-poll. By self-reporting the\n * polling cap, the engine lifts the deadline to `max(self-report,\n * global)` for CR resources only; a user-supplied per-type override\n * (`--resource-timeout AWS::CloudFormation::CustomResource=5m`) still\n * wins for explicit escape-hatching.\n */\n getMinResourceTimeoutMs(): number {\n return this.asyncResponseTimeoutMs;\n }\n\n /**\n * Set the S3 bucket for custom resource responses\n * Called by ProviderRegistry when state bucket is configured\n */\n setResponseBucket(bucket: string, bucketRegion?: string): void {\n this.responseBucket = bucket;\n // For cross-region deploy: S3 client for response bucket must use the bucket's region,\n // not the stack's region. The state bucket is always in the base region.\n if (bucketRegion) {\n this.s3Client = new S3Client(bucketRegion ? { region: bucketRegion } : {});\n }\n }\n\n /**\n * Create a custom resource by invoking its Lambda handler\n */\n async create(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n ): Promise<ResourceCreateResult> {\n this.logger.debug(`Creating custom resource ${logicalId} (${resourceType})`);\n\n const serviceToken = properties['ServiceToken'];\n\n if (!serviceToken) {\n throw new ProvisioningError(\n `ServiceToken is required for custom resource ${logicalId}`,\n resourceType,\n logicalId\n );\n }\n\n if (typeof serviceToken !== 'string') {\n throw new ProvisioningError(\n `Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). ` +\n `This usually indicates state was written by a pre-fix cdkd import; ` +\n `re-run \\`cdkd import\\` or \\`cdkd state orphan <stack>\\` to recover.`,\n resourceType,\n logicalId\n );\n }\n\n try {\n const invocation = await this.prepareInvocation();\n\n const request = {\n RequestType: 'Create',\n RequestId: invocation.requestId,\n ResponseURL: invocation.responseURL,\n ResourceType: resourceType,\n LogicalResourceId: logicalId,\n StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,\n ResourceProperties: this.stringifyProperties(properties),\n };\n\n this.logger.debug(`Sending custom resource create request: ${serviceToken}`);\n\n const cfnResponse = await this.sendRequest(\n serviceToken,\n request,\n invocation.responseKey,\n logicalId,\n 'Create'\n );\n\n if (cfnResponse.Status === 'FAILED') {\n throw new Error(\n `Custom resource handler returned FAILED: ${cfnResponse.Reason || 'Unknown reason'}`\n );\n }\n\n const physicalId: string = cfnResponse.PhysicalResourceId || logicalId;\n const attributes: Record<string, unknown> = cfnResponse.Data || {};\n\n this.logger.debug(`Successfully created custom resource ${logicalId}: ${physicalId}`);\n\n return { physicalId, attributes };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to create custom resource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n undefined,\n cause\n );\n }\n }\n\n /**\n * Update a custom resource by invoking its Lambda handler\n */\n async update(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n previousProperties: Record<string, unknown>\n ): Promise<ResourceUpdateResult> {\n this.logger.debug(`Updating custom resource ${logicalId}: ${physicalId} (${resourceType})`);\n\n const serviceToken = properties['ServiceToken'];\n\n if (!serviceToken) {\n throw new ProvisioningError(\n `ServiceToken is required for custom resource ${logicalId}`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n if (typeof serviceToken !== 'string') {\n throw new ProvisioningError(\n `Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). ` +\n `This usually indicates state was written by a pre-fix cdkd import; ` +\n `re-run \\`cdkd import\\` or \\`cdkd state orphan <stack>\\` to recover.`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n try {\n const invocation = await this.prepareInvocation();\n\n const request = {\n RequestType: 'Update',\n RequestId: invocation.requestId,\n ResponseURL: invocation.responseURL,\n ResourceType: resourceType,\n LogicalResourceId: logicalId,\n PhysicalResourceId: physicalId,\n StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,\n ResourceProperties: this.stringifyProperties(properties),\n OldResourceProperties: this.stringifyProperties(previousProperties),\n };\n\n this.logger.debug(`Sending custom resource update request: ${serviceToken}`);\n\n const cfnResponse = await this.sendRequest(\n serviceToken,\n request,\n invocation.responseKey,\n logicalId,\n 'Update'\n );\n\n if (cfnResponse.Status === 'FAILED') {\n throw new Error(\n `Custom resource handler returned FAILED: ${cfnResponse.Reason || 'Unknown reason'}`\n );\n }\n\n const newPhysicalId: string = cfnResponse.PhysicalResourceId || physicalId;\n const wasReplaced: boolean = newPhysicalId !== physicalId;\n const attributes: Record<string, unknown> = cfnResponse.Data || {};\n\n this.logger.debug(\n `Successfully updated custom resource ${logicalId}: ${newPhysicalId}${wasReplaced ? ' (replaced)' : ''}`\n );\n\n return { physicalId: newPhysicalId, wasReplaced, attributes };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to update custom resource ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n /**\n * Delete a custom resource by invoking its Lambda handler\n */\n async delete(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties?: Record<string, unknown>,\n _context?: DeleteContext\n ): Promise<void> {\n // Custom resources delegate deletion to a user-provided Lambda handler.\n // The Lambda invocation itself does not surface a `*NotFound` for the\n // managed resource, so the region-mismatch check has no signal to act on\n // here; the underlying Lambda's region is determined by its ARN, which is\n // already encoded in the ServiceToken regardless of the cdkd client's\n // region. The context parameter is accepted for interface conformity.\n this.logger.debug(`Deleting custom resource ${logicalId}: ${physicalId} (${resourceType})`);\n\n if (!properties) {\n this.logger.warn(\n `No properties available for custom resource ${logicalId}, skipping deletion`\n );\n return;\n }\n\n const serviceToken = properties['ServiceToken'];\n\n if (!serviceToken) {\n this.logger.warn(`No ServiceToken found for custom resource ${logicalId}, skipping deletion`);\n return;\n }\n\n if (typeof serviceToken !== 'string') {\n throw new ProvisioningError(\n `Custom Resource ${logicalId}: ServiceToken is not a resolved string ARN (got ${typeof serviceToken}). ` +\n `This usually indicates state was written by a pre-fix cdkd import; ` +\n `re-run \\`cdkd import\\` or \\`cdkd state orphan <stack>\\` to recover.`,\n resourceType,\n logicalId,\n physicalId\n );\n }\n\n try {\n const invocation = await this.prepareInvocation();\n\n const request = {\n RequestType: 'Delete',\n RequestId: invocation.requestId,\n ResponseURL: invocation.responseURL,\n ResourceType: resourceType,\n LogicalResourceId: logicalId,\n PhysicalResourceId: physicalId,\n StackId: `arn:aws:cloudformation:us-east-1:000000000000:stack/cdkd-${logicalId}/cdkd`,\n ResourceProperties: this.stringifyProperties(properties),\n };\n\n this.logger.debug(`Sending custom resource delete request: ${serviceToken}`);\n\n const cfnResponse = await this.sendRequest(\n serviceToken,\n request,\n invocation.responseKey,\n logicalId,\n 'Delete'\n );\n\n if (cfnResponse.Status === 'FAILED') {\n this.logger.warn(\n `Custom resource delete handler returned FAILED for ${logicalId}: ${cfnResponse.Reason || 'Unknown reason'}`\n );\n } else {\n this.logger.debug(`Successfully deleted custom resource ${logicalId}`);\n }\n } catch (error) {\n // For deletion, we should be more lenient with errors\n this.logger.warn(\n `Failed to delete custom resource ${logicalId}, but continuing: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Check if a ServiceToken is an SNS topic ARN\n */\n isSnsServiceToken(serviceToken: string): boolean {\n return serviceToken.startsWith('arn:aws:sns:');\n }\n\n /**\n * Send custom resource request via the appropriate service (Lambda or SNS)\n * For Lambda: invokes synchronously and returns the response\n * For SNS: publishes to topic and polls S3 for response\n */\n private async sendRequest(\n serviceToken: string,\n request: Record<string, unknown>,\n responseKey: string,\n logicalId: string,\n operation: string\n ): Promise<CfnCustomResourceResponse> {\n if (this.isSnsServiceToken(serviceToken)) {\n this.logger.debug(`ServiceToken is SNS topic, publishing to: ${serviceToken}`);\n await this.publishToSns(serviceToken, request);\n return await this.pollS3Response(responseKey, logicalId, operation);\n }\n\n // Block until the backing Lambda is in a ready-to-Invoke state. The\n // Lambda CREATE / UPDATE returns synchronously while State / LastUpdateStatus\n // is still `Pending` / `InProgress`; a synchronous Invoke against\n // either fails with \"The function is currently in the following\n // state: Pending\" / \"InProgress\" (see PR #121). We wait HERE — at the\n // one consumer that breaks against not-ready Lambdas — instead of\n // gating every Lambda CREATE on Active, which doubled deploy time on\n // VPC-Lambda benchmark stacks.\n await this.waitForBackingLambdaReady(serviceToken, logicalId);\n\n const response = await this.invokeLambda(serviceToken, request);\n return await this.getCustomResourceResponse(response, responseKey, logicalId, operation);\n }\n\n /**\n * Block until the backing Lambda function for a Custom Resource is in a\n * state that accepts a synchronous Invoke.\n *\n * Two sequential waiters:\n * 1. `waitUntilFunctionActiveV2` — handles the post-CreateFunction\n * `Pending` window (image pull, VPC ENI attachment, layer init).\n * 2. `waitUntilFunctionUpdatedV2` — handles the post-Update\n * `InProgress` window (configuration / code swap settling).\n * Together they cover the only two transient states that reject\n * synchronous Invokes.\n *\n * In the common case (Lambda has been Active for a while, no in-flight\n * Update), both waiters return on first poll → ~2 GetFunction calls →\n * ~200ms overhead. That's the price for correctness; the alternative\n * (whole-stack Active wait at Lambda CREATE) is ~5–10 minutes per\n * VPC-attached function.\n *\n * `serviceToken` is the Lambda function ARN; the Lambda SDK accepts\n * both name and ARN as `FunctionName`, so we pass the ARN through\n * unchanged.\n *\n * `maxWaitTime` is set generously (10 min) because VPC ENI attachment\n * has been observed to take 8+ minutes in pathological cases. The\n * deploy engine's per-resource `--resource-timeout` (default 30 min)\n * still bounds the outer Custom Resource provisioning attempt, so\n * this waiter cap is layered defense, not the only timeout.\n */\n private async waitForBackingLambdaReady(serviceToken: string, logicalId: string): Promise<void> {\n try {\n await waitUntilFunctionActiveV2(\n { client: this.lambdaClient, maxWaitTime: 600 },\n { FunctionName: serviceToken }\n );\n await waitUntilFunctionUpdatedV2(\n { client: this.lambdaClient, maxWaitTime: 600 },\n { FunctionName: serviceToken }\n );\n } catch (error) {\n throw new Error(\n `Lambda backing custom resource ${logicalId} (${serviceToken}) did not reach a ready state for Invoke: ${\n error instanceof Error ? error.message : String(error)\n }`\n );\n }\n }\n\n /**\n * Publish custom resource request to an SNS topic\n */\n private async publishToSns(topicArn: string, request: Record<string, unknown>): Promise<void> {\n await this.snsClient.send(\n new PublishCommand({\n TopicArn: topicArn,\n Message: JSON.stringify(request),\n })\n );\n }\n\n /**\n * Invoke Lambda function synchronously\n */\n private async invokeLambda(\n serviceToken: string,\n request: Record<string, unknown>\n ): Promise<InvocationResponse> {\n return await this.lambdaClient.send(\n new InvokeCommand({\n FunctionName: serviceToken,\n InvocationType: 'RequestResponse',\n Payload: Buffer.from(JSON.stringify(request)),\n })\n );\n }\n\n /**\n * Get custom resource response from either Lambda payload or S3\n *\n * Strategy:\n * 1. If Lambda returned a direct payload with Status field → use it (cfn-response inline)\n * 2. If Lambda returned a payload with PhysicalResourceId → use it (simple handler)\n * 3. Otherwise, poll S3 for the response (cfn-response via ResponseURL)\n */\n private async getCustomResourceResponse(\n lambdaResponse: InvocationResponse,\n responseKey: string,\n logicalId: string,\n operation: string\n ): Promise<CfnCustomResourceResponse> {\n // Check for Lambda execution errors\n if (lambdaResponse.FunctionError) {\n const errorPayload = lambdaResponse.Payload\n ? Buffer.from(lambdaResponse.Payload).toString()\n : 'Unknown';\n throw new Error(`Lambda function error (${lambdaResponse.FunctionError}): ${errorPayload}`);\n }\n\n // Try to parse direct Lambda response\n // Track whether Lambda returned a meaningful payload. If not, this likely indicates\n // an async pattern (e.g., CDK Provider framework with isCompleteHandler that delegates\n // to Step Functions for polling).\n let hasDirectPayload = false;\n try {\n const payload = parseLambdaPayload(lambdaResponse.Payload);\n\n // Check if this is a full cfn-response (has Status field)\n if (\n 'Status' in payload &&\n (payload['Status'] === 'SUCCESS' || payload['Status'] === 'FAILED')\n ) {\n this.logger.debug(`Got direct cfn-response from Lambda for ${logicalId}`);\n await this.cleanupResponseObject(responseKey);\n return payload as unknown as CfnCustomResourceResponse;\n }\n\n // Check if this is a simple handler response (has PhysicalResourceId but no Status)\n if (payload.PhysicalResourceId || payload.Data) {\n this.logger.debug(`Got simple handler response from Lambda for ${logicalId}`);\n await this.cleanupResponseObject(responseKey);\n const result: CfnCustomResourceResponse = {\n Status: 'SUCCESS',\n };\n if (payload.PhysicalResourceId) {\n result.PhysicalResourceId = payload.PhysicalResourceId;\n }\n if (payload.Data) {\n result.Data = payload.Data;\n }\n return result;\n }\n\n // Payload parsed but contained no recognizable fields (e.g., empty object from\n // CDK Provider framework after starting Step Functions). Mark as no direct payload.\n hasDirectPayload = Object.keys(payload).length > 0;\n } catch {\n // Payload parsing failed, try S3\n this.logger.debug(`Lambda payload parse failed for ${logicalId}, checking S3 response`);\n }\n\n // Poll S3 for response (cfn-response module sends to ResponseURL)\n if (!this.responseBucket) {\n this.logger.warn(\n `No response bucket configured for custom resource ${logicalId}. ` +\n `The Lambda handler likely uses cfn-response module which sends to ResponseURL. ` +\n `Configure --state-bucket to enable S3-based response handling.`\n );\n return {\n Status: 'SUCCESS',\n PhysicalResourceId: logicalId,\n };\n }\n\n // Detect async custom resource pattern (CDK Provider framework with isCompleteHandler).\n // When the framework Lambda starts a Step Functions state machine for async polling,\n // it returns no meaningful payload (empty/null). In this case, the Step Functions\n // will eventually PUT the cfn-response to the ResponseURL, which may take up to\n // the configured totalTimeout (default: 1 hour in CDK).\n // We use a longer timeout for this case vs the short timeout for synchronous handlers.\n const isAsyncPattern = !hasDirectPayload;\n if (isAsyncPattern) {\n this.logger.debug(\n `Custom resource ${logicalId} uses async Provider framework. ` +\n `Waiting up to ${Math.round(this.asyncResponseTimeoutMs / 60_000)} minutes.`\n );\n } else {\n this.logger.debug(`Waiting for S3 response from Lambda for ${logicalId} (${operation})`);\n }\n\n const timeoutMs = isAsyncPattern ? this.asyncResponseTimeoutMs : this.SYNC_RESPONSE_TIMEOUT_MS;\n return await this.pollS3Response(responseKey, logicalId, operation, timeoutMs, isAsyncPattern);\n }\n\n /**\n * Prepare a single Custom Resource invocation: generate the request id,\n * derive the S3 response key from it, sign the pre-signed PUT URL for that\n * key, and return all three together.\n *\n * **The request id, response key, and response URL must all be derived from\n * the SAME generation step.** Previously these were generated by separate\n * calls inside `create` / `update` / `delete`, which made it possible for a\n * future refactor (e.g. wrapping URL signing in a retry that re-rolls the\n * id) to silently break the invariant — the Lambda would write to one S3\n * key while cdkd polled a different one, hanging the deploy until the\n * polling timeout (up to 1 hour). See issue #90.\n *\n * Centralising this in one helper makes that invariant impossible to\n * violate at the call sites.\n */\n private async prepareInvocation(): Promise<{\n requestId: string;\n responseKey: string;\n responseURL: string;\n }> {\n const requestId = `cdkd-${Date.now()}-${Math.random().toString(36).substring(7)}`;\n const responseKey = this.getResponseKey(requestId);\n const responseURL = await this.generateResponseURL(responseKey);\n return { requestId, responseKey, responseURL };\n }\n\n /**\n * Generate a pre-signed S3 PUT URL for Lambda to send its response\n */\n private async generateResponseURL(responseKey: string): Promise<string> {\n if (!this.responseBucket) {\n // Fallback: return a dummy URL (legacy behavior)\n return 'https://localhost/cfn-response-not-configured';\n }\n\n // Create an empty placeholder object first (so the key exists for cleanup)\n await this.s3Client.send(\n new PutObjectCommand({\n Bucket: this.responseBucket,\n Key: responseKey,\n Body: '',\n ContentLength: 0,\n ContentType: 'application/json',\n })\n );\n\n // Generate pre-signed PUT URL (valid for 2 hours to accommodate async Provider framework\n // patterns where Step Functions may poll isCompleteHandler for up to 1 hour)\n // Don't specify ContentType so any Content-Type is accepted (cfn-response may send different types)\n const command = new PutObjectCommand({\n Bucket: this.responseBucket,\n Key: responseKey,\n });\n\n const presignedUrl = await getSignedUrl(this.s3Client, command, {\n expiresIn: 7200,\n });\n\n this.logger.debug(\n `Generated pre-signed URL for response: s3://${this.responseBucket}/${responseKey}`\n );\n return presignedUrl;\n }\n\n /**\n * Poll S3 for the custom resource response\n *\n * Uses exponential backoff for polling interval:\n * - Sync mode (standard handlers): starts at 2s, no backoff (short timeout)\n * - Async mode (Provider framework with isCompleteHandler): starts at 2s, backs off to 30s max\n *\n * @param responseKey S3 key where response will be written\n * @param logicalId Logical resource ID for logging\n * @param operation Operation type (Create/Update/Delete) for logging\n * @param timeoutMs Maximum time to wait for response\n * @param useBackoff Whether to use exponential backoff (for async/long-running operations)\n */\n private async pollS3Response(\n responseKey: string,\n logicalId: string,\n operation: string,\n timeoutMs: number = this.SYNC_RESPONSE_TIMEOUT_MS,\n useBackoff: boolean = false\n ): Promise<CfnCustomResourceResponse> {\n const startTime = Date.now();\n let currentInterval = this.INITIAL_POLL_INTERVAL_MS;\n let pollCount = 0;\n\n // Listen for SIGINT to abort polling early\n let interrupted = false;\n const sigintHandler = () => {\n interrupted = true;\n };\n process.on('SIGINT', sigintHandler);\n\n try {\n while (Date.now() - startTime < timeoutMs) {\n if (interrupted) {\n await this.cleanupResponseObject(responseKey);\n process.removeListener('SIGINT', sigintHandler);\n throw new Error(`Custom resource ${logicalId} interrupted by user`);\n }\n\n pollCount++;\n try {\n const response = await this.s3Client.send(\n new GetObjectCommand({\n Bucket: this.responseBucket!,\n Key: responseKey,\n })\n );\n\n const body = await response.Body?.transformToString();\n if (body && body.length > 0) {\n this.logger.debug(`Got S3 response for ${logicalId}: ${body.substring(0, 200)}`);\n\n try {\n const cfnResponse = JSON.parse(body) as CfnCustomResourceResponse;\n\n // Validate response has required fields\n if (cfnResponse.Status === 'SUCCESS' || cfnResponse.Status === 'FAILED') {\n // Cleanup the response object\n await this.cleanupResponseObject(responseKey);\n return cfnResponse;\n }\n } catch {\n // JSON parse failed, response not yet written properly\n this.logger.debug(`S3 response not yet valid JSON for ${logicalId}, retrying...`);\n }\n }\n } catch (error) {\n const err = error as { name?: string };\n if (err.name !== 'NoSuchKey') {\n this.logger.debug(`Error reading S3 response for ${logicalId}: ${err.name}`);\n }\n }\n\n await this.sleep(currentInterval);\n\n // Apply exponential backoff for async patterns (long-running operations)\n if (useBackoff) {\n currentInterval = Math.min(currentInterval * 1.5, this.MAX_POLL_INTERVAL_MS);\n\n // Log progress periodically for long-running operations\n if (pollCount % 10 === 0) {\n const elapsedSec = Math.round((Date.now() - startTime) / 1000);\n this.logger.info(\n `Still waiting for async custom resource ${logicalId} (${operation})... ` +\n `${elapsedSec}s elapsed, polling every ${Math.round(currentInterval / 1000)}s`\n );\n }\n }\n }\n\n // Cleanup on timeout\n await this.cleanupResponseObject(responseKey);\n\n const elapsedMin = Math.round((Date.now() - startTime) / 60_000);\n throw new Error(\n `Timeout waiting for custom resource response for ${logicalId} (${operation}) ` +\n `after ${elapsedMin} minutes. ` +\n (useBackoff\n ? `The async custom resource handler (Provider framework with isCompleteHandler) did not complete within the timeout. ` +\n `Check the Step Functions execution and isCompleteHandler Lambda logs for errors.`\n : `The Lambda handler may not be sending a response to ResponseURL.`)\n );\n } finally {\n process.removeListener('SIGINT', sigintHandler);\n }\n }\n\n /**\n * Get S3 key for response object\n */\n private getResponseKey(requestId: string): string {\n return `${this.responsePrefix}/${requestId}.json`;\n }\n\n /**\n * Cleanup response object from S3\n */\n private async cleanupResponseObject(responseKey: string): Promise<void> {\n if (!this.responseBucket) return;\n\n try {\n await this.s3Client.send(\n new DeleteObjectCommand({\n Bucket: this.responseBucket,\n Key: responseKey,\n })\n );\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /**\n * Convert property values to strings for CloudFormation compatibility\n *\n * CloudFormation converts all ResourceProperties values to strings before\n * passing them to Lambda handlers. Some CDK internal handlers (like\n * BucketNotificationsHandler) depend on this behavior (e.g., calling .lower()\n * on boolean values).\n */\n private stringifyProperties(properties: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(properties)) {\n if (typeof value === 'boolean') {\n result[key] = String(value);\n } else if (typeof value === 'number') {\n result[key] = String(value);\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n result[key] = this.stringifyProperties(value as Record<string, unknown>);\n } else {\n result[key] = value;\n }\n }\n return result;\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n /**\n * Adopt an existing custom resource into cdkd state.\n *\n * **Explicit override only.** A custom resource's identity is the\n * `PhysicalResourceId` returned by its user-supplied Lambda handler at\n * Create time — there is no AWS-side resource cdkd can introspect, no\n * tag API, and no `aws:cdk:path` to look up by. cdkd cannot rediscover\n * a custom resource without invoking the handler, which would mutate\n * state.\n *\n * Users adopting an existing custom resource should pass\n * `--resource <logicalId>=<physicalResourceId>` — the same value the\n * handler returned originally.\n */\n // eslint-disable-next-line @typescript-eslint/require-await -- explicit-override-only intentionally has no AWS calls\n async import(input: ResourceImportInput): Promise<ResourceImportResult | null> {\n if (input.knownPhysicalId) {\n return { physicalId: input.knownPhysicalId, attributes: {} };\n }\n return null;\n }\n}\n","import type { ResourceProvider } from '../types/resource.js';\nimport { CloudControlProvider } from './cloud-control-provider.js';\nimport { CustomResourceProvider } from './providers/custom-resource-provider.js';\nimport { getLogger } from '../utils/logger.js';\n\n/**\n * Provider registry for managing resource providers\n *\n * Implements a fallback strategy:\n * 1. Try specific SDK provider if registered for this resource type\n * 2. Fall back to Cloud Control API if resource type is supported\n * 3. Throw error if no provider available\n */\nexport class ProviderRegistry {\n private logger = getLogger().child('ProviderRegistry');\n private providers = new Map<string, ResourceProvider>();\n private cloudControlProvider: CloudControlProvider;\n private customResourceProvider: CustomResourceProvider;\n private skipResourceTypes = new Set<string>();\n\n constructor() {\n this.cloudControlProvider = new CloudControlProvider();\n this.customResourceProvider = new CustomResourceProvider();\n }\n\n /**\n * Configure the response bucket for custom resources\n * This allows Lambda handlers using cfn-response to send responses via S3\n */\n setCustomResourceResponseBucket(bucket: string, bucketRegion?: string): void {\n this.customResourceProvider.setResponseBucket(bucket, bucketRegion);\n this.logger.debug(`Custom resource response bucket set to: ${bucket}`);\n }\n\n /**\n * Register a resource type to be skipped during deployment\n *\n * @param resourceType CloudFormation resource type to skip\n */\n skipResourceType(resourceType: string): void {\n this.logger.debug(`Registering ${resourceType} to be skipped`);\n this.skipResourceTypes.add(resourceType);\n }\n\n /**\n * Register a specific provider for a resource type\n *\n * @param resourceType CloudFormation resource type (e.g., \"AWS::S3::Bucket\")\n * @param provider Provider instance\n */\n register(resourceType: string, provider: ResourceProvider): void {\n this.logger.debug(`Registering provider for ${resourceType}`);\n this.providers.set(resourceType, provider);\n }\n\n /**\n * Unregister a provider for a resource type\n */\n unregister(resourceType: string): void {\n this.logger.debug(`Unregistering provider for ${resourceType}`);\n this.providers.delete(resourceType);\n }\n\n /**\n * Get provider for a resource type\n *\n * Selection strategy:\n * 1. If specific SDK provider is registered, use it\n * 2. Otherwise, use Cloud Control API if supported\n * 3. Throw error if no provider available\n *\n * @param resourceType CloudFormation resource type\n * @returns Provider instance\n * @throws Error if no provider available\n */\n getProvider(resourceType: string): ResourceProvider {\n // 1. Check for specific SDK provider\n const specificProvider = this.providers.get(resourceType);\n if (specificProvider) {\n this.logger.debug(`Using specific SDK provider for ${resourceType}`);\n return specificProvider;\n }\n\n // 2. Check if Cloud Control API supports this resource type\n if (CloudControlProvider.isSupportedResourceType(resourceType)) {\n this.logger.debug(`Using Cloud Control API provider for ${resourceType}`);\n return this.cloudControlProvider;\n }\n\n // 3. Check if it's a custom resource (Custom:: prefix or AWS::CloudFormation::CustomResource)\n if (\n resourceType.startsWith('Custom::') ||\n resourceType === 'AWS::CloudFormation::CustomResource'\n ) {\n this.logger.debug(`Using Custom Resource provider for ${resourceType}`);\n return this.customResourceProvider;\n }\n\n // 4. No provider available\n throw new Error(\n `No provider available for resource type: ${resourceType}. ` +\n `This resource type is not supported by Cloud Control API and no SDK provider is registered.`\n );\n }\n\n /**\n * Check if a resource type should be skipped\n */\n shouldSkipResource(resourceType: string): boolean {\n return this.skipResourceTypes.has(resourceType);\n }\n\n /**\n * Check if a provider is available for a resource type\n */\n hasProvider(resourceType: string): boolean {\n // Skipped resources are considered as \"having a provider\" to avoid validation errors\n if (this.shouldSkipResource(resourceType)) {\n return true;\n }\n return (\n this.providers.has(resourceType) ||\n CloudControlProvider.isSupportedResourceType(resourceType) ||\n resourceType.startsWith('Custom::') ||\n resourceType === 'AWS::CloudFormation::CustomResource'\n );\n }\n\n /**\n * Get the Cloud Control provider instance (for resource state lookup)\n */\n getCloudControlProvider(): CloudControlProvider {\n return this.cloudControlProvider;\n }\n\n /**\n * Get all registered resource types (excluding Cloud Control)\n */\n getRegisteredTypes(): string[] {\n return Array.from(this.providers.keys());\n }\n\n /**\n * Get provider type for a resource type\n *\n * @returns 'sdk' | 'cloud-control' | null\n */\n getProviderType(resourceType: string): 'sdk' | 'cloud-control' | null {\n if (this.providers.has(resourceType)) {\n return 'sdk';\n }\n if (CloudControlProvider.isSupportedResourceType(resourceType)) {\n return 'cloud-control';\n }\n return null;\n }\n\n /**\n * Validate that all resource types have available providers\n *\n * This should be called before deployment starts to ensure all resources can be provisioned.\n *\n * @param resourceTypes Set of resource types to validate\n * @throws Error if any resource type doesn't have a provider\n */\n validateResourceTypes(resourceTypes: Set<string>): void {\n const unsupportedTypes: string[] = [];\n\n for (const resourceType of resourceTypes) {\n if (!this.hasProvider(resourceType)) {\n unsupportedTypes.push(resourceType);\n }\n }\n\n if (unsupportedTypes.length > 0) {\n throw new Error(\n `The following resource types are not supported:\\n` +\n unsupportedTypes.map((type) => ` - ${type}`).join('\\n') +\n `\\n\\nThese resource types are not supported by Cloud Control API and no SDK provider is registered.\\n` +\n `Please report this issue at https://github.com/go-to-k/cdkd/issues so we can add SDK provider support.`\n );\n }\n\n this.logger.debug(\n `Validated ${resourceTypes.size} resource types: all have available providers`\n );\n }\n}\n","/**\n * Shared helpers for `ResourceProvider.import` implementations.\n *\n * Most providers follow the same lookup pattern when adopting an\n * already-deployed resource into cdkd state:\n *\n * 1. If `input.knownPhysicalId` is set (user passed\n * `--resource <logicalId>=<physicalId>`), trust it as ground truth and\n * only fetch attributes.\n * 2. If the template's `properties` carries an explicit name field\n * (`BucketName`, `FunctionName`, `RoleName`, …), use that as the\n * physical id directly.\n * 3. Walk the service's `List*` API and match against the `aws:cdk:path`\n * tag — every CDK-deployed resource carries one.\n *\n * Step 1 + 2 are generic enough to live here. Step 3 needs per-service\n * `List*` + `ListTags*` calls and lives in each provider.\n */\n\nimport type { ResourceImportInput } from '../types/resource.js';\n\n/**\n * Read an explicit name field from template properties. Returns `undefined`\n * when the property is missing or not a string — callers fall back to\n * tag-based lookup in that case.\n */\nexport function readNameProperty(\n input: ResourceImportInput,\n propertyName: string\n): string | undefined {\n const value = input.properties?.[propertyName];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * Resolve the physical id when the template provides an explicit name OR the\n * caller passed `--resource`/`--resource-mapping`. Returns `undefined` when\n * neither shortcut applies — caller must then fall back to tag-based lookup.\n *\n * Does NOT verify the resource exists: callers should follow up with a\n * service-specific `Head*`/`Get*`/`Describe*` to fail fast if the named\n * resource is missing.\n */\nexport function resolveExplicitPhysicalId(\n input: ResourceImportInput,\n nameProperty: string | null\n): string | undefined {\n if (input.knownPhysicalId) return input.knownPhysicalId;\n if (nameProperty) {\n const name = readNameProperty(input, nameProperty);\n if (name) return name;\n }\n return undefined;\n}\n\n/**\n * The standard tag CDK puts on every deployed resource — its construct path\n * within the app, e.g. `MyStack/MyConstruct/MyBucket`. Used as the lookup key\n * when no explicit name is in the template.\n */\nexport const CDK_PATH_TAG = 'aws:cdk:path';\n\n/**\n * Loose tag shape used by every AWS service (`{Key, Value}`).\n *\n * Both Key and Value are typed as `string | undefined` rather than `string?`\n * so this interface accepts AWS SDK v3 `Tag` types verbatim under\n * `exactOptionalPropertyTypes: true` (the SDK types declare them as\n * `Key?: string | undefined`, not `Key?: string`).\n */\nexport interface AwsTag {\n Key?: string | undefined;\n Value?: string | undefined;\n}\n\n/**\n * Match an AWS resource's tag set against the CDK path the template carries.\n * Returns true if the resource was deployed by the same CDK construct.\n */\nexport function matchesCdkPath(tags: readonly AwsTag[] | undefined, cdkPath: string): boolean {\n if (!tags || !cdkPath) return false;\n for (const t of tags) {\n if (t.Key === CDK_PATH_TAG && t.Value === cdkPath) return true;\n }\n return false;\n}\n\n/**\n * Re-shape an AWS tag list (any of the common shapes — array of `{Key, Value}`,\n * map keyed by tag name, or v2-style array of `{TagKey, TagValue}`) into the\n * canonical CFn shape (`Array<{Key, Value}>`) that cdkd state holds, with\n * `aws:`-prefixed entries filtered out.\n *\n * AWS reserves the `aws:` tag prefix; CDK injects `aws:cdk:path` (and\n * sometimes `aws:cdk:metadata`) on every resource it deploys. Those tags are\n * NOT in cdkd state's `Tags` (they come from CDK template `Metadata`, not\n * `Properties.Tags`), so leaving them in the AWS-current snapshot would fire\n * false-positive drift on every CDK-deployed resource.\n *\n * Returns an empty array `[]` when AWS reports no user tags. Callers decide\n * whether to surface `Tags: []` (most providers — matches the typical\n * CFn behavior of always emitting Tags in templates) or omit the key\n * entirely (when the corresponding `create()` only sets Tags when the user\n * explicitly passes them — see each provider's docstring).\n */\nexport function normalizeAwsTagsToCfn(\n tags:\n | readonly AwsTag[]\n | readonly { TagKey?: string | undefined; TagValue?: string | undefined }[]\n | readonly { key?: string | undefined; value?: string | undefined }[]\n | Record<string, string | undefined>\n | undefined\n | null\n): Array<{ Key: string; Value: string }> {\n if (!tags) return [];\n const out: Array<{ Key: string; Value: string }> = [];\n if (Array.isArray(tags)) {\n for (const t of tags) {\n // Support {Key,Value} (most services), {TagKey,TagValue} (RDS/DocDB),\n // and {key,value} (Step Functions / Glue / etc., lower-case).\n const obj = t as Record<string, unknown>;\n const k =\n (typeof obj['Key'] === 'string' ? obj['Key'] : undefined) ??\n (typeof obj['TagKey'] === 'string' ? obj['TagKey'] : undefined) ??\n (typeof obj['key'] === 'string' ? obj['key'] : undefined);\n const v =\n (typeof obj['Value'] === 'string' ? obj['Value'] : undefined) ??\n (typeof obj['TagValue'] === 'string' ? obj['TagValue'] : undefined) ??\n (typeof obj['value'] === 'string' ? obj['value'] : undefined);\n if (typeof k !== 'string' || k.length === 0) continue;\n if (k.startsWith('aws:')) continue;\n out.push({ Key: k, Value: typeof v === 'string' ? v : '' });\n }\n } else {\n for (const [k, v] of Object.entries(tags)) {\n if (!k || k.startsWith('aws:')) continue;\n out.push({ Key: k, Value: typeof v === 'string' ? v : '' });\n }\n }\n // Sort by Key for stable comparison against state (CDK templates\n // produce sorted Tags; AWS API responses are unordered).\n out.sort((a, b) => (a.Key < b.Key ? -1 : a.Key > b.Key ? 1 : 0));\n return out;\n}\n","import {\n IAMClient,\n CreateRoleCommand,\n UpdateRoleCommand,\n UpdateAssumeRolePolicyCommand,\n DeleteRoleCommand,\n GetRoleCommand,\n GetRolePolicyCommand,\n PutRolePolicyCommand,\n DeleteRolePolicyCommand,\n ListRolePoliciesCommand,\n AttachRolePolicyCommand,\n DetachRolePolicyCommand,\n ListAttachedRolePoliciesCommand,\n ListInstanceProfilesForRoleCommand,\n RemoveRoleFromInstanceProfileCommand,\n TagRoleCommand,\n UntagRoleCommand,\n PutRolePermissionsBoundaryCommand,\n DeleteRolePermissionsBoundaryCommand,\n ListRolesCommand,\n ListRoleTagsCommand,\n NoSuchEntityException,\n} from '@aws-sdk/client-iam';\nimport { getLogger } from '../../utils/logger.js';\nimport { getAwsClients } from '../../utils/aws-clients.js';\nimport { ProvisioningError } from '../../utils/error-handler.js';\nimport { assertRegionMatch, type DeleteContext } from '../region-check.js';\nimport { generateResourceNameWithFallback } from '../resource-name.js';\nimport {\n matchesCdkPath,\n normalizeAwsTagsToCfn,\n resolveExplicitPhysicalId,\n} from '../import-helpers.js';\nimport type {\n ResourceProvider,\n ResourceCreateResult,\n ResourceUpdateResult,\n ResourceImportInput,\n ResourceImportResult,\n} from '../../types/resource.js';\n\n/**\n * AWS IAM Role Provider\n *\n * Implements resource provisioning for AWS::IAM::Role using the IAM SDK.\n * This is required because IAM Role is not supported by Cloud Control API.\n */\nexport class IAMRoleProvider implements ResourceProvider {\n private iamClient: IAMClient;\n private logger = getLogger().child('IAMRoleProvider');\n handledProperties = new Map<string, ReadonlySet<string>>([\n [\n 'AWS::IAM::Role',\n new Set([\n 'RoleName',\n 'AssumeRolePolicyDocument',\n 'Description',\n 'MaxSessionDuration',\n 'Path',\n 'PermissionsBoundary',\n 'ManagedPolicyArns',\n 'Policies',\n 'Tags',\n ]),\n ],\n ]);\n\n constructor() {\n // Use global AWS clients manager for better resource management\n const awsClients = getAwsClients();\n this.iamClient = awsClients.iam;\n }\n\n /**\n * Create an IAM role\n */\n async create(\n logicalId: string,\n resourceType: string,\n properties: Record<string, unknown>\n ): Promise<ResourceCreateResult> {\n this.logger.debug(`Creating IAM role ${logicalId}`);\n\n const roleName = generateResourceNameWithFallback(\n properties['RoleName'] as string | undefined,\n logicalId,\n { maxLength: 64 }\n );\n const assumeRolePolicyDocument = properties['AssumeRolePolicyDocument'];\n\n if (!assumeRolePolicyDocument) {\n throw new ProvisioningError(\n `AssumeRolePolicyDocument is required for IAM role ${logicalId}`,\n resourceType,\n logicalId\n );\n }\n\n try {\n // Serialize policy document\n const policyDocument =\n typeof assumeRolePolicyDocument === 'string'\n ? assumeRolePolicyDocument\n : JSON.stringify(assumeRolePolicyDocument);\n\n // Create role\n const createParams: {\n RoleName: string;\n AssumeRolePolicyDocument: string;\n Description?: string;\n MaxSessionDuration?: number;\n Path?: string;\n PermissionsBoundary?: string;\n } = {\n RoleName: roleName,\n AssumeRolePolicyDocument: policyDocument,\n };\n\n if (properties['Description']) {\n createParams.Description = properties['Description'] as string;\n }\n if (properties['MaxSessionDuration']) {\n createParams.MaxSessionDuration = properties['MaxSessionDuration'] as number;\n }\n if (properties['Path']) {\n createParams.Path = properties['Path'] as string;\n }\n if (properties['PermissionsBoundary']) {\n createParams.PermissionsBoundary = properties['PermissionsBoundary'] as string;\n }\n\n const response = await this.iamClient.send(new CreateRoleCommand(createParams));\n\n this.logger.debug(`Created IAM role: ${roleName}`);\n\n // Attach managed policies if specified\n const managedPolicyArns = properties['ManagedPolicyArns'] as string[] | undefined;\n if (managedPolicyArns && Array.isArray(managedPolicyArns)) {\n for (const policyArn of managedPolicyArns) {\n await this.iamClient.send(\n new AttachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policyArn,\n })\n );\n this.logger.debug(`Attached managed policy ${policyArn} to role ${roleName}`);\n }\n }\n\n // Add inline policies if specified\n const policies = properties['Policies'] as\n | Array<{ PolicyName: string; PolicyDocument: unknown }>\n | undefined;\n if (policies && Array.isArray(policies)) {\n for (const policy of policies) {\n const policyDoc =\n typeof policy.PolicyDocument === 'string'\n ? policy.PolicyDocument\n : JSON.stringify(policy.PolicyDocument);\n\n await this.iamClient.send(\n new PutRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policy.PolicyName,\n PolicyDocument: policyDoc,\n })\n );\n this.logger.debug(`Added inline policy ${policy.PolicyName} to role ${roleName}`);\n }\n }\n\n // Add tags if specified\n const tags = properties['Tags'] as Array<{ Key: string; Value: string }> | undefined;\n if (tags && Array.isArray(tags)) {\n await this.iamClient.send(\n new TagRoleCommand({\n RoleName: roleName,\n Tags: tags,\n })\n );\n this.logger.debug(`Tagged role ${roleName}`);\n }\n\n this.logger.debug(`Successfully created IAM role ${logicalId}: ${roleName}`);\n\n const attributes = {\n Arn: response.Role?.Arn,\n RoleId: response.Role?.RoleId,\n };\n\n return {\n physicalId: roleName,\n attributes,\n };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to create IAM role ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n roleName,\n cause\n );\n }\n }\n\n /**\n * Update an IAM role\n */\n async update(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n properties: Record<string, unknown>,\n previousProperties: Record<string, unknown>\n ): Promise<ResourceUpdateResult> {\n this.logger.debug(`Updating IAM role ${logicalId}: ${physicalId}`);\n\n const newRoleName = generateResourceNameWithFallback(\n properties['RoleName'] as string | undefined,\n logicalId,\n { maxLength: 64 }\n );\n\n // Check if immutable properties changed (requires replacement)\n // RoleName and Path are immutable - cannot be changed after creation\n const newPath = (properties['Path'] as string | undefined) || '/';\n const oldPath = (previousProperties['Path'] as string | undefined) || '/';\n const needsReplacement = newRoleName !== physicalId || newPath !== oldPath;\n\n if (needsReplacement) {\n const reason = newRoleName !== physicalId ? 'RoleName' : 'Path';\n this.logger.debug(\n `${reason} changed, replacing role: ${physicalId} (${reason}: ${reason === 'RoleName' ? `${physicalId} -> ${newRoleName}` : `${oldPath} -> ${newPath}`})`\n );\n\n // Create new role\n const createResult = await this.create(logicalId, resourceType, properties);\n\n // Delete old role with full cleanup (managed policies, inline policies, instance profiles)\n try {\n await this.delete(logicalId, physicalId, resourceType);\n } catch (error) {\n this.logger.warn(\n `Failed to delete old role ${physicalId} during replacement: ${String(error)}. ` +\n `The old role may be orphaned and require manual cleanup.`\n );\n }\n\n const result: ResourceUpdateResult = {\n physicalId: createResult.physicalId,\n wasReplaced: true,\n };\n\n if (createResult.attributes) {\n result.attributes = createResult.attributes;\n }\n\n return result;\n }\n\n try {\n // Update role properties (Description, MaxSessionDuration)\n const updateParams: {\n RoleName: string;\n Description?: string;\n MaxSessionDuration?: number;\n } = {\n RoleName: physicalId,\n };\n\n // `!== undefined` (not truthy) so an empty Description ('') reaches\n // `UpdateRoleCommand`, which the AWS API documents as the way to\n // clear an existing description. A truthy gate would silently drop\n // the empty string and leave the AWS-side description untouched —\n // surfaced as a `cdkd drift --revert` that reports `✓ reverted`\n // but the very next `cdkd drift` re-detects the same drift.\n if (properties['Description'] !== undefined) {\n updateParams.Description = properties['Description'] as string;\n }\n if (properties['MaxSessionDuration'] !== undefined) {\n updateParams.MaxSessionDuration = properties['MaxSessionDuration'] as number;\n }\n\n await this.iamClient.send(new UpdateRoleCommand(updateParams));\n\n // Update AssumeRolePolicyDocument if changed\n const newAssumePolicy = properties['AssumeRolePolicyDocument'];\n const oldAssumePolicy = previousProperties['AssumeRolePolicyDocument'];\n if (newAssumePolicy) {\n const newPolicyStr =\n typeof newAssumePolicy === 'string' ? newAssumePolicy : JSON.stringify(newAssumePolicy);\n const oldPolicyStr = oldAssumePolicy\n ? typeof oldAssumePolicy === 'string'\n ? oldAssumePolicy\n : JSON.stringify(oldAssumePolicy)\n : '';\n\n if (newPolicyStr !== oldPolicyStr) {\n await this.iamClient.send(\n new UpdateAssumeRolePolicyCommand({\n RoleName: physicalId,\n PolicyDocument: newPolicyStr,\n })\n );\n this.logger.debug(`Updated assume role policy for ${physicalId}`);\n }\n }\n\n // Update PermissionsBoundary\n const newBoundary = properties['PermissionsBoundary'] as string | undefined;\n const oldBoundary = previousProperties['PermissionsBoundary'] as string | undefined;\n if (newBoundary !== oldBoundary) {\n if (newBoundary) {\n await this.iamClient.send(\n new PutRolePermissionsBoundaryCommand({\n RoleName: physicalId,\n PermissionsBoundary: newBoundary,\n })\n );\n this.logger.debug(`Set permissions boundary for ${physicalId}: ${newBoundary}`);\n } else if (oldBoundary) {\n await this.iamClient.send(\n new DeleteRolePermissionsBoundaryCommand({\n RoleName: physicalId,\n })\n );\n this.logger.debug(`Removed permissions boundary from ${physicalId}`);\n }\n }\n\n // Update managed policies\n await this.updateManagedPolicies(\n physicalId,\n properties['ManagedPolicyArns'] as string[] | undefined,\n previousProperties['ManagedPolicyArns'] as string[] | undefined\n );\n\n // Update inline policies\n await this.updateInlinePolicies(\n physicalId,\n properties['Policies'] as\n | Array<{ PolicyName: string; PolicyDocument: unknown }>\n | undefined,\n previousProperties['Policies'] as\n | Array<{ PolicyName: string; PolicyDocument: unknown }>\n | undefined\n );\n\n // Update tags\n await this.updateTags(\n physicalId,\n properties['Tags'] as Array<{ Key: string; Value: string }> | undefined,\n previousProperties['Tags'] as Array<{ Key: string; Value: string }> | undefined\n );\n\n this.logger.debug(`Successfully updated IAM role ${logicalId}`);\n\n // Get updated role info\n const getRoleResponse = await this.iamClient.send(\n new GetRoleCommand({ RoleName: physicalId })\n );\n\n const attributes = {\n Arn: getRoleResponse.Role?.Arn,\n RoleId: getRoleResponse.Role?.RoleId,\n };\n\n return {\n physicalId,\n wasReplaced: false,\n attributes,\n };\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to update IAM role ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n /**\n * Delete an IAM role\n *\n * Before deleting, performs full cleanup:\n * 1. Detach all managed policies\n * 2. Delete all inline policies\n * 3. Remove role from all instance profiles\n * 4. Delete the role itself\n */\n async delete(\n logicalId: string,\n physicalId: string,\n resourceType: string,\n _properties?: Record<string, unknown>,\n context?: DeleteContext\n ): Promise<void> {\n this.logger.debug(`Deleting IAM role ${logicalId}: ${physicalId}`);\n\n try {\n // Check if role exists\n try {\n await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n const clientRegion = await this.iamClient.config.region();\n assertRegionMatch(\n clientRegion,\n context?.expectedRegion,\n resourceType,\n logicalId,\n physicalId\n );\n this.logger.debug(`Role ${physicalId} does not exist, skipping deletion`);\n return;\n }\n throw error;\n }\n\n // Step 1: Detach all managed policies\n await this.detachAllManagedPolicies(physicalId);\n\n // Step 2: Delete all inline policies\n await this.deleteAllInlinePolicies(physicalId);\n\n // Step 3: Remove role from all instance profiles\n await this.removeFromAllInstanceProfiles(physicalId);\n\n // Step 4: Delete the role\n await this.iamClient.send(new DeleteRoleCommand({ RoleName: physicalId }));\n\n this.logger.debug(`Successfully deleted IAM role ${logicalId}`);\n } catch (error) {\n const cause = error instanceof Error ? error : undefined;\n throw new ProvisioningError(\n `Failed to delete IAM role ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,\n resourceType,\n logicalId,\n physicalId,\n cause\n );\n }\n }\n\n /**\n * Detach all managed policies from the role\n */\n private async detachAllManagedPolicies(roleName: string): Promise<void> {\n this.logger.debug(`Detaching all managed policies from role ${roleName}`);\n\n try {\n const attachedPolicies = await this.iamClient.send(\n new ListAttachedRolePoliciesCommand({ RoleName: roleName })\n );\n\n const policies = attachedPolicies.AttachedPolicies || [];\n if (policies.length === 0) {\n this.logger.debug(`No managed policies attached to role ${roleName}`);\n return;\n }\n\n for (const policy of policies) {\n if (policy.PolicyArn) {\n try {\n await this.iamClient.send(\n new DetachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policy.PolicyArn,\n })\n );\n this.logger.debug(`Detached managed policy ${policy.PolicyArn} from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(\n `Managed policy ${policy.PolicyArn} already detached from role ${roleName}`\n );\n } else {\n throw error;\n }\n }\n }\n }\n\n this.logger.debug(`Detached ${policies.length} managed policies from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Role ${roleName} not found when detaching managed policies`);\n return;\n }\n throw error;\n }\n }\n\n /**\n * Delete all inline policies from the role\n */\n private async deleteAllInlinePolicies(roleName: string): Promise<void> {\n this.logger.debug(`Deleting all inline policies from role ${roleName}`);\n\n try {\n const inlinePolicies = await this.iamClient.send(\n new ListRolePoliciesCommand({ RoleName: roleName })\n );\n\n const policyNames = inlinePolicies.PolicyNames || [];\n if (policyNames.length === 0) {\n this.logger.debug(`No inline policies on role ${roleName}`);\n return;\n }\n\n for (const policyName of policyNames) {\n try {\n await this.iamClient.send(\n new DeleteRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policyName,\n })\n );\n this.logger.debug(`Deleted inline policy ${policyName} from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Inline policy ${policyName} already deleted from role ${roleName}`);\n } else {\n throw error;\n }\n }\n }\n\n this.logger.debug(`Deleted ${policyNames.length} inline policies from role ${roleName}`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Role ${roleName} not found when deleting inline policies`);\n return;\n }\n throw error;\n }\n }\n\n /**\n * Remove the role from all instance profiles\n */\n private async removeFromAllInstanceProfiles(roleName: string): Promise<void> {\n this.logger.debug(`Removing role ${roleName} from all instance profiles`);\n\n try {\n const instanceProfiles = await this.iamClient.send(\n new ListInstanceProfilesForRoleCommand({ RoleName: roleName })\n );\n\n const profiles = instanceProfiles.InstanceProfiles || [];\n if (profiles.length === 0) {\n this.logger.debug(`No instance profiles associated with role ${roleName}`);\n return;\n }\n\n for (const profile of profiles) {\n if (profile.InstanceProfileName) {\n try {\n await this.iamClient.send(\n new RemoveRoleFromInstanceProfileCommand({\n RoleName: roleName,\n InstanceProfileName: profile.InstanceProfileName,\n })\n );\n this.logger.debug(\n `Removed role ${roleName} from instance profile ${profile.InstanceProfileName}`\n );\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(\n `Role ${roleName} already removed from instance profile ${profile.InstanceProfileName}`\n );\n } else {\n throw error;\n }\n }\n }\n }\n\n this.logger.debug(`Removed role ${roleName} from ${profiles.length} instance profiles`);\n } catch (error) {\n if (error instanceof NoSuchEntityException) {\n this.logger.debug(`Role ${roleName} not found when removing from instance profiles`);\n return;\n }\n throw error;\n }\n }\n\n /**\n * Update managed policies attached to role\n */\n private async updateManagedPolicies(\n roleName: string,\n newPolicies: string[] | undefined,\n oldPolicies: string[] | undefined\n ): Promise<void> {\n const newSet = new Set(newPolicies || []);\n const oldSet = new Set(oldPolicies || []);\n\n // Attach new policies\n for (const policyArn of newSet) {\n if (!oldSet.has(policyArn)) {\n await this.iamClient.send(\n new AttachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policyArn,\n })\n );\n this.logger.debug(`Attached managed policy ${policyArn}`);\n }\n }\n\n // Detach removed policies\n for (const policyArn of oldSet) {\n if (!newSet.has(policyArn)) {\n await this.iamClient.send(\n new DetachRolePolicyCommand({\n RoleName: roleName,\n PolicyArn: policyArn,\n })\n );\n this.logger.debug(`Detached managed policy ${policyArn}`);\n }\n }\n }\n\n /**\n * Update inline policies\n */\n private async updateInlinePolicies(\n roleName: string,\n newPolicies: Array<{ PolicyName: string; PolicyDocument: unknown }> | undefined,\n oldPolicies: Array<{ PolicyName: string; PolicyDocument: unknown }> | undefined\n ): Promise<void> {\n const newMap = new Map((newPolicies || []).map((p) => [p.PolicyName, p.PolicyDocument]));\n const oldMap = new Map((oldPolicies || []).map((p) => [p.PolicyName, p.PolicyDocument]));\n\n // Add or update policies\n for (const [policyName, policyDoc] of newMap) {\n const policyDocument = typeof policyDoc === 'string' ? policyDoc : JSON.stringify(policyDoc);\n\n await this.iamClient.send(\n new PutRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policyName,\n PolicyDocument: policyDocument,\n })\n );\n this.logger.debug(`Updated inline policy ${policyName}`);\n }\n\n // Delete removed policies\n for (const policyName of oldMap.keys()) {\n if (!newMap.has(policyName)) {\n await this.iamClient.send(\n new DeleteRolePolicyCommand({\n RoleName: roleName,\n PolicyName: policyName,\n })\n );\n this.logger.debug(`Deleted inline policy ${policyName}`);\n }\n }\n }\n\n /**\n * Update tags on the role\n */\n private async updateTags(\n roleName: string,\n newTags: Array<{ Key: string; Value: string }> | undefined,\n oldTags: Array<{ Key: string; Value: string }> | undefined\n ): Promise<void> {\n const newTagMap = new Map((newTags || []).map((t) => [t.Key, t.Value]));\n const oldTagMap = new Map((oldTags || []).map((t) => [t.Key, t.Value]));\n\n // Find tags to remove (present in old but not in new)\n const tagsToRemove: string[] = [];\n for (const key of oldTagMap.keys()) {\n if (!newTagMap.has(key)) {\n tagsToRemove.push(key);\n }\n }\n\n // Find tags to add/update (new or changed value)\n const tagsToAdd: Array<{ Key: string; Value: string }> = [];\n for (const [key, value] of newTagMap) {\n if (oldTagMap.get(key) !== value) {\n tagsToAdd.push({ Key: key, Value: value });\n }\n }\n\n if (tagsToRemove.length > 0) {\n await this.iamClient.send(\n new UntagRoleCommand({\n RoleName: roleName,\n TagKeys: tagsToRemove,\n })\n );\n this.logger.debug(`Removed ${tagsToRemove.length} tags from role ${roleName}`);\n }\n\n if (tagsToAdd.length > 0) {\n await this.iamClient.send(\n new TagRoleCommand({\n RoleName: roleName,\n Tags: tagsToAdd,\n })\n );\n this.logger.debug(`Added/updated ${tagsToAdd.length} tags on role ${roleName}`);\n }\n }\n\n /**\n * Resolve a single `Fn::GetAtt` attribute for an existing IAM role.\n *\n * CloudFormation's `AWS::IAM::Role` exposes `Arn` and `RoleId`; both are\n * available from the `GetRole` response. See:\n * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#aws-resource-iam-role-return-values\n *\n * Used by `cdkd orphan` to live-fetch attribute values that need to be\n * substituted into sibling references.\n */\n async getAttribute(\n physicalId: string,\n _resourceType: string,\n attributeName: string\n ): Promise<unknown> {\n try {\n const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));\n switch (attributeName) {\n case 'Arn':\n return resp.Role?.Arn;\n case 'RoleId':\n return resp.Role?.RoleId;\n default:\n return undefined;\n }\n } catch (err) {\n if (err instanceof NoSuchEntityException) return undefined;\n throw err;\n }\n }\n\n /**\n * Read the AWS-current IAM role configuration in CFn-property shape.\n *\n * Issues `GetRole` for the top-level role configuration and\n * `ListRolePolicies` + `ListAttachedRolePolicies` for inline / managed\n * policy *names*. AWS URL-decodes `AssumeRolePolicyDocument` for us\n * when it surfaces — we re-parse it as JSON so the comparator can match\n * against state's already-parsed object.\n *\n * Coverage and shape decisions:\n * - `RoleName`, `Description`, `MaxSessionDuration`, `Path` — straight\n * from `Role.*`.\n * - `PermissionsBoundary` — emitted as `'' ` placeholder when AWS has\n * none, so a console-side ADD on a role that was deployed without a\n * boundary surfaces as drift. (The drift comparator's top-level walk\n * is state-keys-only; without the always-emit placeholder a fresh\n * `PermissionsBoundary` on the AWS side would never enter\n * `observedProperties` and the comparator would silently ignore it.)\n * - `AssumeRolePolicyDocument` — `Role.AssumeRolePolicyDocument` is a\n * URL-encoded JSON string; we URL-decode + JSON-parse so cdkd state's\n * object form compares cleanly. (Both shapes — string and object — are\n * accepted by `create()`, but state typically stores the parsed object\n * after intrinsic resolution.)\n * - `ManagedPolicyArns` — array of ARN strings from\n * `ListAttachedRolePolicies`.\n * - `Policies` — inline policies surfaced as `[{PolicyName, PolicyDocument}]`.\n * `ListRolePolicies` for names + `GetRolePolicy` per name for the\n * body (URL-decoded + JSON-parsed). Ordering is reconciled against\n * state's `Policies` array (when supplied via the `properties`\n * parameter) so a state-vs-AWS positional compare doesn't fire false\n * drift purely from `ListRolePolicies` returning lexicographic order;\n * AWS-only policies (added via console) are appended at the end so\n * they still surface as drift via length / content mismatch.\n * - `Tags` is surfaced via `ListRoleTags` (paginated). CDK's `aws:*`\n * auto-tags are filtered out by `normalizeAwsTagsToCfn` so they don't\n * fire false-positive drift; always emitted (even when empty) so a\n * console-side tag ADD on an originally-untagged role surfaces as\n * drift on the v3 observedProperties baseline.\n *\n * Returns `undefined` when the role is gone (`NoSuchEntityException`).\n */\n async readCurrentState(\n physicalId: string,\n _logicalId: string,\n _resourceType: string,\n properties?: Record<string, unknown>,\n context?: import('../../types/resource.js').ReadCurrentStateContext\n ): Promise<Record<string, unknown> | undefined> {\n let role;\n try {\n const resp = await this.iamClient.send(new GetRoleCommand({ RoleName: physicalId }));\n role = resp.Role;\n } catch (err) {\n if (err instanceof NoSuchEntityException) return undefined;\n throw err;\n }\n if (!role) return undefined;\n\n const result: Record<string, unknown> = {};\n\n if (role.RoleName !== undefined) result['RoleName'] = role.RoleName;\n result['Description'] = role.Description ?? '';\n if (role.MaxSessionDuration !== undefined) {\n result['MaxSessionDuration'] = role.MaxSessionDuration;\n }\n if (role.Path !== undefined) result['Path'] = role.Path;\n // Always-emit (PR #145 pattern): surfaces console-side ADDs on roles\n // deployed without a boundary. AWS returns the boundary as a nested\n // `{ PermissionsBoundaryArn, PermissionsBoundaryType }` shape; cdkd\n // state stores the bare ARN string (matches CFn input shape).\n result['PermissionsBoundary'] = role.PermissionsBoundary?.PermissionsBoundaryArn ?? '';\n if (role.AssumeRolePolicyDocument) {\n // GetRole returns AssumeRolePolicyDocument URL-encoded. Decode and\n // parse so the comparator can match cdkd state (which holds the\n // already-resolved object form).\n try {\n result['AssumeRolePolicyDocument'] = JSON.parse(\n decodeURIComponent(role.AssumeRolePolicyDocument)\n ) as unknown;\n } catch {\n // Fall back to the raw string if decoding / parsing fails. The\n // comparator handles primitive vs object mismatches correctly.\n result['AssumeRolePolicyDocument'] = role.AssumeRolePolicyDocument;\n }\n }\n\n // ManagedPolicyArns — string[] of attached managed policy ARNs.\n try {\n const attached = await this.iamClient.send(\n new ListAttachedRolePoliciesCommand({ RoleName: physicalId })\n );\n const arns = (attached.AttachedPolicies ?? [])\n .map((p) => p.PolicyArn)\n .filter((arn): arn is string => !!arn);\n result['ManagedPolicyArns'] = arns;\n } catch (err) {\n if (!(err instanceof NoSuchEntityException)) throw err;\n }\n\n // Inline Policies — `[{PolicyName, PolicyDocument}]`. Cap at IAM's\n // documented 10-inline-policies-per-role limit to bound the API\n // budget; ListRolePolicies is paginated for forward-compat anyway.\n try {\n const policyNames: string[] = [];\n let policyMarker: string | undefined;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const listResp = await this.iamClient.send(\n new ListRolePoliciesCommand({\n RoleName: physicalId,\n ...(policyMarker ? { Marker: policyMarker } : {}),\n })\n );\n for (const name of listResp.PolicyNames ?? []) policyNames.push(name);\n if (!listResp.IsTruncated) break;\n policyMarker = listResp.Marker;\n }\n\n // Issue #323: filter out inline policies that are managed by a\n // SEPARATE `AWS::IAM::Policy` resource attached via `Roles: [role]`.\n // CDK's `iam.Policy({ roles: [r] })` (and L2 helpers like\n // `taskRole.addToPolicy(...)` / `bucket.grantRead(role)` /\n // `ContainerImage.fromEcrRepository(repo)`'s execution-role grant)\n // creates a separate `AWS::IAM::Policy` resource — which AWS\n // implements via `iam:PutRolePolicy`, so those inline policies\n // appear in `ListRolePolicies` output. Without this filter every\n // such Role fires false drift (state.Policies = [] vs aws =\n // [{...DefaultPolicy*}]).\n const managedByOtherResource = collectInlinePolicyNamesManagedBySiblings(\n physicalId,\n context,\n 'Roles'\n );\n const filteredNames = policyNames.filter((n) => !managedByOtherResource.has(n));\n\n // Fetch every body in parallel (max 10; well under any IAM rate\n // limit). URL-decode + JSON-parse so the comparator sees the same\n // object shape state holds after intrinsic resolution.\n const bodies = new Map<string, unknown>();\n await Promise.all(\n filteredNames.map(async (name) => {\n const resp = await this.iamClient.send(\n new GetRolePolicyCommand({ RoleName: physicalId, PolicyName: name })\n );\n if (!resp.PolicyDocument) return;\n let parsed: unknown;\n try {\n parsed = JSON.parse(decodeURIComponent(resp.PolicyDocument));\n } catch {\n parsed = resp.PolicyDocument;\n }\n bodies.set(name, parsed);\n })\n );\n\n // Reconcile order against state's `Policies` so a positional array\n // compare doesn't fire purely from `ListRolePolicies` returning\n // lexicographic order. AWS-only entries (console adds) tail-append\n // so length / content mismatch still surfaces them as drift.\n const statePolicies =\n (properties?.['Policies'] as Array<{ PolicyName?: string }> | undefined) ?? [];\n const remaining = new Set(bodies.keys());\n const inline: Array<{ PolicyName: string; PolicyDocument: unknown }> = [];\n for (const sp of statePolicies) {\n const name = sp?.PolicyName;\n if (typeof name !== 'string') continue;\n if (bodies.has(name)) {\n inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });\n remaining.delete(name);\n }\n }\n for (const name of [...remaining].sort()) {\n inline.push({ PolicyName: name, PolicyDocument: bodies.get(name) });\n }\n result['Policies'] = inline;\n } catch (err) {\n if (!(err instanceof NoSuchEntityException)) throw err;\n }\n\n // Tags via ListRoleTags. Paginated — small page sizes are fine since\n // IAM enforces a 50-tag-per-role limit, but we still iterate Marker for\n // forward-compat.\n try {\n const collected: Array<{ Key?: string | undefined; Value?: string | undefined }> = [];\n let marker: string | undefined;\n // eslint-disable-next-line no-constant-condition\n while (true) {\n const tagsResp = await this.iamClient.send(\n new ListRoleTagsCommand({\n RoleName: physicalId,\n ...(marker ? { Marker: marker } : {}),\n })\n );\n if (tagsResp.Tags) {\n for (const t of tagsResp.Tags) {\n collected.push({ Key: t.Key, Value: t.Value });\n }\n }\n if (!tagsResp.IsTruncated) break;\n marker = tagsResp.Marker;\n }\n const tags = normalizeAwsTagsToCfn(collected);\n result['Tags'] = tags;\n } catch (err) {\n if (!(err instanceof NoSuchEntityException)) throw err;\n }\n\n return result;\n }\n\n /**\n * Adopt an existing IAM role into cdkd state.\n *\n * Lookup order:\n * 1. `--resource` override or `Properties.RoleName` → use directly,\n * verify via `GetRole`.\n * 2. `ListRoles` + `ListRoleTags`, match `aws:cdk:path` tag.\n *\n * `ListRoles` is paginated and IAM is global (no region scoping), so this\n * walks every role in the account once. Acceptable for the cardinalities\n * we expect (typically <100 roles per account); larger accounts may want\n * to provide `--resource` overrides instead.\n */\n async import(input: ResourceImportInput): Promise<ResourceImportResult | null> {\n const explicit = resolveExplicitPhysicalId(input, 'RoleName');\n if (explicit) {\n try {\n await this.iamClient.send(new GetRoleCommand({ RoleName: explicit }));\n return { physicalId: explicit, attributes: {} };\n } catch (err) {\n if (err instanceof NoSuchEntityException) return null;\n throw err;\n }\n }\n\n if (!input.cdkPath) return null;\n\n let marker: string | undefined;\n do {\n const list = await this.iamClient.send(\n new ListRolesCommand({ ...(marker && { Marker: marker }) })\n );\n for (const role of list.Roles ?? []) {\n if (!role.RoleName) continue;\n try {\n const tags = await this.iamClient.send(\n new ListRoleTagsCommand({ RoleName: role.RoleName })\n );\n if (matchesCdkPath(tags.Tags, input.cdkPath)) {\n return { physicalId: role.RoleName, attributes: {} };\n }\n } catch (err) {\n if (err instanceof NoSuchEntityException) continue;\n throw err;\n }\n }\n marker = list.IsTruncated ? list.Marker : undefined;\n } while (marker);\n return null;\n }\n}\n\n/**\n * Issue #323: build the set of inline-policy names that are managed by\n * a sibling `AWS::IAM::Policy` resource in the same stack via the given\n * attachment field (`Roles` / `Users` / `Groups`). cdkd's IAM Role /\n * User / Group `readCurrentState` helpers exclude these from\n * `ListRolePolicies` / `ListUserPolicies` / `ListGroupPolicies` output\n * to avoid false drift — the inline policy is faithfully managed by\n * the sibling `AWS::IAM::Policy` resource, not the role/user/group\n * itself. The CDK patterns that produce this shape are pervasive:\n * `role.addToPolicy(...)`, `taskRole.addToPolicy(...)`,\n * `bucket.grantRead(role)`, `ContainerImage.fromEcrRepository(repo)`'s\n * execution-role grant, every L2-construct's auto-emitted `Default\n * Policy*`.\n *\n * @param targetPhysicalId The physicalId of the role/user/group being\n * read (matches values in the sibling's\n * `Properties.Roles` / `Users` / `Groups`).\n * @param context Cross-resource context (may be `undefined`\n * for callers that don't supply it — e.g.\n * deploy-time observed-capture before state\n * is complete; the filter then no-ops which\n * is safe because the sibling's\n * `PutRolePolicy` hasn't fired yet at that\n * point).\n * @param attachmentField Which sibling field to inspect: `'Roles'`,\n * `'Users'`, or `'Groups'`.\n * @returns Set of `PolicyName` values to exclude. Empty when no\n * sibling matches OR when context is undefined.\n */\nexport function collectInlinePolicyNamesManagedBySiblings(\n targetPhysicalId: string,\n context: import('../../types/resource.js').ReadCurrentStateContext | undefined,\n attachmentField: 'Roles' | 'Users' | 'Groups'\n): Set<string> {\n const result = new Set<string>();\n const siblings = context?.siblings;\n if (!siblings) return result;\n for (const sibling of Object.values(siblings)) {\n if (sibling.resourceType !== 'AWS::IAM::Policy') continue;\n const attachments = sibling.properties[attachmentField];\n if (!Array.isArray(attachments)) continue;\n if (!attachments.some((a) => a === targetPhysicalId)) continue;\n const name = sibling.properties['PolicyName'];\n if (typeof name === 'string') result.add(name);\n }\n return result;\n}\n","import { getLogger } from '../utils/logger.js';\n\nexport type DagNodeState = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';\n\nexport interface DagNode<T = unknown> {\n id: string;\n dependencies: Set<string>;\n state: DagNodeState;\n data: T;\n}\n\n/**\n * Event-driven DAG executor with bounded concurrency.\n *\n * Dispatches a node as soon as ALL of its dependencies are completed —\n * unlike level-synchronized execution, downstream work does not wait for\n * unrelated siblings in the same \"level\" to finish.\n *\n * Failure handling:\n * - A failed node marks its transitive downstream as 'skipped' (not started)\n * - In-flight nodes drain naturally; no new dispatch after first failure\n * (cancelled() can be set to halt dispatch — used for SIGINT)\n * - On drain, rejects with the FIRST failure (matches prior behavior)\n *\n * Cancellation:\n * - When cancelled() returns true, no new nodes are started.\n * In-flight nodes complete normally. After drain, resolves cleanly\n * if no errors — caller is responsible for translating cancellation\n * into a thrown error (e.g., InterruptedError on SIGINT).\n *\n * Dependencies pointing to nodes outside the registered set are treated\n * as already-completed (e.g., NO_CHANGE resources excluded from the DAG).\n */\nexport class DagExecutor<T = unknown> {\n private nodes = new Map<string, DagNode<T>>();\n private logger = getLogger().child('DagExecutor');\n\n add(node: DagNode<T>): void {\n this.nodes.set(node.id, node);\n }\n\n has(id: string): boolean {\n return this.nodes.has(id);\n }\n\n size(): number {\n return this.nodes.size;\n }\n\n values(): IterableIterator<DagNode<T>> {\n return this.nodes.values();\n }\n\n async execute(\n concurrency: number,\n fn: (node: DagNode<T>) => Promise<void>,\n cancelled: () => boolean = () => false\n ): Promise<void> {\n let active = 0;\n const errors: Array<{ id: string; error: unknown }> = [];\n\n return new Promise<void>((resolve, reject) => {\n const dispatch = (): void => {\n // Mark nodes whose dependencies failed/skipped as skipped — to a\n // fixed point, so transitive dependents propagate within a single\n // dispatch (e.g., A→B→C where A failed must mark BOTH B and C as\n // skipped, regardless of node insertion order).\n let changed = true;\n while (changed) {\n changed = false;\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const hasFailedDep = [...node.dependencies].some((depId) => {\n const dep = this.nodes.get(depId);\n return dep && (dep.state === 'failed' || dep.state === 'skipped');\n });\n if (hasFailedDep) {\n node.state = 'skipped';\n changed = true;\n this.logger.debug(`Skipped ${node.id}: dependency failed or was skipped`);\n }\n }\n }\n\n // Find ready nodes (deps completed or external-to-DAG).\n const ready: DagNode<T>[] = [];\n for (const node of this.nodes.values()) {\n if (node.state !== 'pending') continue;\n const depsReady = [...node.dependencies].every((depId) => {\n const dep = this.nodes.get(depId);\n return !dep || dep.state === 'completed';\n });\n if (depsReady) ready.push(node);\n }\n\n // Dispatch up to concurrency limit, unless cancellation requested.\n if (!cancelled()) {\n for (const node of ready) {\n if (active >= concurrency) break;\n node.state = 'running';\n active++;\n\n fn(node)\n .then(() => {\n node.state = 'completed';\n })\n .catch((error) => {\n node.state = 'failed';\n errors.push({ id: node.id, error });\n })\n .finally(() => {\n active--;\n dispatch();\n });\n }\n }\n\n if (active === 0) {\n // Drain-before-reject guarantee: we only reach this point after every\n // in-flight node has settled (success OR failure), because each fn()\n // promise's .finally() decrements `active` and re-runs dispatch. So\n // when a node fails early, sibling nodes already running are allowed\n // to complete normally — their successful completion is visible to\n // the caller (e.g., for state-save and rollback bookkeeping) BEFORE\n // execute() rejects. Don't change to \"reject as soon as errors[] is\n // non-empty\" without revisiting the deploy-engine catch path.\n if (errors.length > 0) {\n reject(errors[0]!.error);\n return;\n }\n const stillPending = [...this.nodes.values()].some((n) => n.state === 'pending');\n if (stillPending && !cancelled()) {\n const pending = [...this.nodes.values()]\n .filter((n) => n.state === 'pending')\n .map((n) => n.id);\n reject(\n new Error(\n `Deadlock detected: ${pending.length} node(s) stuck with unresolvable dependencies (${pending.join(', ')})`\n )\n );\n return;\n }\n resolve();\n }\n };\n\n dispatch();\n });\n }\n}\n","/**\n * Type-based implicit deletion dependency rules.\n *\n * CloudFormation expresses creation order via Ref / Fn::GetAtt / DependsOn.\n * For deletion, AWS additionally enforces ordering rules that aren't visible\n * in those references — for example, an InternetGateway can't be deleted\n * while it's still attached to a VPC, even though the attachment Ref's the\n * IGW (not the other way around). This module centralizes those type-based\n * rules so that both the deploy engine (DELETE phase) and the destroy\n * command apply the same ordering.\n *\n * Each entry maps `KEY` → list of types that must be deleted BEFORE the\n * KEY type. Reading example:\n *\n * 'AWS::EC2::Subnet': ['AWS::Lambda::Function']\n *\n * = \"every Subnet in this stack must be deleted AFTER every Lambda in\n * this stack\" — required because Lambda's VpcConfig leaves an ENI in\n * the subnet for some time after the function is deleted, and tearing\n * the subnet down first triggers a DependencyViolation.\n */\nexport const IMPLICIT_DELETE_DEPENDENCIES: Record<string, readonly string[]> = {\n // IGW must be deleted AFTER VPCGatewayAttachment\n 'AWS::EC2::InternetGateway': ['AWS::EC2::VPCGatewayAttachment'],\n\n // EventBus must be deleted AFTER Rules on that bus\n 'AWS::Events::EventBus': ['AWS::Events::Rule'],\n\n // Athena workgroup must be deleted AFTER its named queries\n 'AWS::Athena::WorkGroup': ['AWS::Athena::NamedQuery'],\n\n // CloudFront managed-policy-style resources must be deleted AFTER\n // any Distribution that references them\n 'AWS::CloudFront::ResponseHeadersPolicy': ['AWS::CloudFront::Distribution'],\n 'AWS::CloudFront::CachePolicy': ['AWS::CloudFront::Distribution'],\n 'AWS::CloudFront::OriginAccessControl': ['AWS::CloudFront::Distribution'],\n\n // VPC must be deleted AFTER all VPC-dependent resources\n 'AWS::EC2::VPC': [\n 'AWS::EC2::Subnet',\n 'AWS::EC2::SecurityGroup',\n 'AWS::EC2::InternetGateway',\n 'AWS::EC2::EgressOnlyInternetGateway',\n 'AWS::EC2::VPCGatewayAttachment',\n 'AWS::EC2::RouteTable',\n ],\n\n // Subnet must be deleted AFTER any Lambda that may still hold an ENI\n // in it. Lambda DELETE returns immediately but the ENI is detached\n // asynchronously by AWS, so deleting the Subnet first races the detach\n // and yields \"DependencyViolation\".\n 'AWS::EC2::Subnet': ['AWS::EC2::SubnetRouteTableAssociation', 'AWS::Lambda::Function'],\n\n // RouteTable must be deleted AFTER Route and Association\n 'AWS::EC2::RouteTable': ['AWS::EC2::Route', 'AWS::EC2::SubnetRouteTableAssociation'],\n\n // SecurityGroup must be deleted AFTER any Lambda whose ENI is bound\n // to it (same ENI-detach race as Subnet above).\n 'AWS::EC2::SecurityGroup': [\n 'AWS::EC2::SecurityGroupIngress',\n 'AWS::EC2::SecurityGroupEgress',\n 'AWS::Lambda::Function',\n ],\n};\n","/**\n * Patterns that mark an AWS error as a transient/retryable failure.\n * Each entry is a substring match against the error message; all of these\n * are situations where the same call typically succeeds after a short delay\n * because of eventual consistency or just-created-dependency propagation.\n */\nexport const RETRYABLE_ERROR_MESSAGE_PATTERNS: readonly string[] = [\n // IAM propagation\n 'cannot be assumed',\n 'role defined for the function',\n 'not authorized to perform',\n 'execution role',\n 'trust policy',\n 'Role validation failed',\n 'does not have required permissions',\n 'Trusted Entity',\n 'currently in the following state: Pending',\n // DELETE dependency ordering (parallel deletion race conditions)\n 'has dependencies and cannot be deleted',\n \"can't be deleted since it has\",\n 'DependencyViolation',\n // AWS eventual consistency (dependency just created but not yet visible)\n // e.g., RDS DBCluster referencing a just-created DBSubnetGroup\n 'does not exist',\n // AppSync schema is being created asynchronously\n 'Schema is currently being altered',\n // IAM principal not yet propagated to S3 bucket policy\n 'Invalid principal in policy',\n // S3 bucket creation/deletion still in progress\n 'conflicting conditional operation',\n // Secrets Manager: ForceDeleteWithoutRecovery may take a moment to propagate\n 'scheduled for deletion',\n // DynamoDB Streams / Kinesis: IAM role not yet propagated\n 'Cannot access stream',\n 'Please ensure the role can perform',\n // KMS: IAM role not yet propagated for CreateGrant\n 'KMS key is invalid for CreateGrant',\n // CloudWatch Logs SubscriptionFilter: Kinesis stream eventual consistency\n // or SubscriptionFilter role propagation. CW Logs probes the destination\n // by delivering a test message; if the stream is freshly ACTIVE or the\n // assumed role hasn't propagated, the probe fails with \"Invalid request\".\n 'Could not deliver test message',\n // SQS: same-name queue can't be re-created until 60s after a delete.\n // Hits when a stack is destroyed and re-deployed in quick succession\n // (a common dev / iteration loop). Retry recovers within ~60s instead\n // of failing the whole deploy.\n 'wait 60 seconds',\n];\n\n/**\n * HTTP status codes that always indicate a transient failure worth retrying.\n * 429 = Too Many Requests (throttle), 503 = Service Unavailable.\n */\nexport const RETRYABLE_HTTP_STATUS_CODES: ReadonlySet<number> = new Set([429, 503]);\n\n/**\n * Determine whether an AWS error should be retried.\n *\n * Checks (in order):\n * 1. HTTP status code on the error itself (`$metadata.httpStatusCode`)\n * 2. HTTP status code on a wrapped cause (`cause.$metadata.httpStatusCode`)\n * 3. Substring match against {@link RETRYABLE_ERROR_MESSAGE_PATTERNS}\n */\nexport function isRetryableTransientError(error: unknown, message: string): boolean {\n const metadata = (error as { $metadata?: { httpStatusCode?: number } }).$metadata;\n const statusCode = metadata?.httpStatusCode;\n if (statusCode !== undefined && RETRYABLE_HTTP_STATUS_CODES.has(statusCode)) return true;\n\n const cause = (error as { cause?: { $metadata?: { httpStatusCode?: number } } }).cause;\n const causeStatus = cause?.$metadata?.httpStatusCode;\n if (causeStatus !== undefined && RETRYABLE_HTTP_STATUS_CODES.has(causeStatus)) return true;\n\n return RETRYABLE_ERROR_MESSAGE_PATTERNS.some((p) => message.includes(p));\n}\n","/**\n * Retry helper for resource provisioning operations that hit transient\n * AWS eventual-consistency errors (IAM propagation, Lambda Pending state,\n * dependency violations, etc.).\n *\n * Extracted from DeployEngine so the backoff schedule can be unit-tested\n * in isolation. The retryable-error classifier itself lives in\n * `./retryable-errors.ts`.\n */\n\nimport { isRetryableTransientError } from './retryable-errors.js';\n\nexport interface RetryLogger {\n debug(message: string): void;\n}\n\nexport interface WithRetryOptions {\n /** Max number of retries after the first attempt. Defaults to 8. */\n maxRetries?: number;\n /**\n * Initial backoff in milliseconds. Subsequent retries double it\n * (1s -> 2s -> 4s -> ... at the default of 1_000ms).\n *\n * The default of 1_000ms is tuned for the typical AWS eventual-consistency\n * window of 2-5s (IAM trust-policy propagation, freshly-created Lambda\n * leaving Pending state). A longer initial delay (e.g. 10s) adds idle time\n * on the deploy critical path even when the underlying window is much\n * shorter.\n */\n initialDelayMs?: number;\n /**\n * Cap for the per-retry delay in milliseconds. Once the doubling schedule\n * reaches this value it stays flat instead of growing further. Defaults to\n * 8_000ms.\n *\n * Why cap: IAM propagation has a long-ish tail (occasional 20-30s waits\n * past the typical 2-5s window). Pure exponential backoff turns a single\n * stalled propagation into 16s, 32s, 64s waits — far more than the\n * underlying window. Capping at 8s lets us still poll roughly every 8s\n * once we're past the early ramp-up, recovering as soon as AWS stabilises.\n */\n maxDelayMs?: number;\n /** Optional debug logger; receives one line per retry attempt. */\n logger?: RetryLogger;\n /**\n * Optional interrupt check — invoked once per second while sleeping.\n * Throws an interrupt error (e.g. on SIGINT) to abort the retry loop early.\n */\n isInterrupted?: () => boolean;\n /** Thrown when `isInterrupted()` returns true mid-sleep. */\n onInterrupted?: () => Error;\n /** Override the sleep implementation (used by tests to skip real waits). */\n sleep?: (ms: number) => Promise<void>;\n}\n\nconst defaultSleep = (ms: number): Promise<void> =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Run `operation`, retrying transient failures with exponential backoff\n * capped at `maxDelayMs`.\n *\n * Backoff at the defaults (initialDelayMs=1_000, maxDelayMs=8_000, maxRetries=8):\n * 1s -> 2s -> 4s -> 8s -> 8s -> 8s -> 8s -> 8s (cumulative 47s)\n *\n * Non-retryable errors are rethrown immediately. The transient-error\n * classifier is `isRetryableTransientError` from ./retryable-errors.ts.\n */\nexport async function withRetry<T>(\n operation: () => Promise<T>,\n logicalId: string,\n opts: WithRetryOptions = {}\n): Promise<T> {\n const maxRetries = opts.maxRetries ?? 8;\n const initialDelayMs = opts.initialDelayMs ?? 1_000;\n const maxDelayMs = opts.maxDelayMs ?? 8_000;\n const sleep = opts.sleep ?? defaultSleep;\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await operation();\n } catch (error) {\n lastError = error;\n const message = error instanceof Error ? error.message : String(error);\n\n const retryable = isRetryableTransientError(error, message);\n if (!retryable || attempt >= maxRetries) {\n throw error;\n }\n\n const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);\n opts.logger?.debug(\n ` ⏳ Retrying ${logicalId} in ${delay / 1000}s (attempt ${attempt + 1}/${maxRetries}) - ${message}`\n );\n\n // Interruptible sleep: check for SIGINT every second during delay.\n for (let waited = 0; waited < delay; waited += 1000) {\n if (opts.isInterrupted?.()) {\n throw opts.onInterrupted ? opts.onInterrupted() : new Error('Interrupted');\n }\n await sleep(Math.min(1000, delay - waited));\n }\n }\n }\n\n throw lastError;\n}\n","/**\n * Per-resource wall-clock deadline + warn timer for provider operations.\n *\n * Wraps a single provider call (CREATE / UPDATE / DELETE) so the deploy\n * engine can enforce `--resource-timeout` and `--resource-warn-after`\n * without each provider needing to plumb timeouts through itself.\n *\n * Mechanism:\n * - A `setTimeout` fires `onWarn(elapsedMs)` once at `warnAfterMs`.\n * - A `setTimeout` fires `onTimeout(elapsedMs)` once at `timeoutMs` and\n * causes the wrapper's outer promise to reject with the error returned\n * by `onTimeout`.\n * - When the wrapped operation settles first, both timers are cleared\n * and neither callback fires.\n *\n * Caveat: this is a `Promise.race`-style abort, not a true cancellation.\n * The underlying provider call keeps running for some additional time\n * after the timer fires — that is documented and accepted; threading\n * `AbortController` through every provider is out of scope for v1.\n */\nexport interface ResourceDeadlineOptions {\n /** Milliseconds after which to fire `onWarn` once. */\n warnAfterMs: number;\n /** Milliseconds after which to abort with `onTimeout`. */\n timeoutMs: number;\n /**\n * Called once when the operation has been running longer than\n * `warnAfterMs`. Receives the elapsed milliseconds (≈ `warnAfterMs`).\n * No-op default; callers typically mutate the live renderer's task\n * label and emit a `logger.warn` line.\n */\n onWarn?: (elapsedMs: number) => void;\n /**\n * Called when the operation exceeds `timeoutMs`. Must return the\n * `Error` to reject the outer promise with. Receives elapsed\n * milliseconds (≈ `timeoutMs`).\n */\n onTimeout: (elapsedMs: number) => Error;\n}\n\n/**\n * Validation error thrown synchronously when option values are nonsensical\n * (`timeoutMs <= warnAfterMs`, non-positive, NaN). Keeps the helper safe\n * to use even in tests that pass raw numbers.\n */\nexport class InvalidResourceDeadlineError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidResourceDeadlineError';\n }\n}\n\nfunction validateOptions(opts: ResourceDeadlineOptions): void {\n const { warnAfterMs, timeoutMs } = opts;\n if (\n !Number.isFinite(warnAfterMs) ||\n !Number.isFinite(timeoutMs) ||\n warnAfterMs <= 0 ||\n timeoutMs <= 0\n ) {\n throw new InvalidResourceDeadlineError(\n `withResourceDeadline: warnAfterMs and timeoutMs must be positive finite numbers ` +\n `(got warnAfterMs=${warnAfterMs}, timeoutMs=${timeoutMs})`\n );\n }\n if (warnAfterMs >= timeoutMs) {\n throw new InvalidResourceDeadlineError(\n `withResourceDeadline: warnAfterMs (${warnAfterMs}ms) must be less than timeoutMs (${timeoutMs}ms)`\n );\n }\n}\n\n/**\n * Run `operation` under a wall-clock deadline.\n *\n * Resolves with the operation's result if it settles within `timeoutMs`.\n * Rejects with the result of `opts.onTimeout(elapsedMs)` otherwise. If\n * the operation throws after the timeout has already fired, the timeout\n * error wins (we never overwrite the rejection with a late provider\n * error).\n */\nexport async function withResourceDeadline<T>(\n operation: () => Promise<T>,\n opts: ResourceDeadlineOptions\n): Promise<T> {\n validateOptions(opts);\n\n const startedAt = Date.now();\n\n return new Promise<T>((resolve, reject) => {\n let settled = false;\n let warnTimer: NodeJS.Timeout | undefined;\n let timeoutTimer: NodeJS.Timeout | undefined;\n\n const cleanup = (): void => {\n if (warnTimer !== undefined) clearTimeout(warnTimer);\n if (timeoutTimer !== undefined) clearTimeout(timeoutTimer);\n warnTimer = undefined;\n timeoutTimer = undefined;\n };\n\n if (opts.onWarn) {\n warnTimer = setTimeout(() => {\n if (settled) return;\n try {\n opts.onWarn!(Date.now() - startedAt);\n } catch {\n // onWarn is best-effort UX — never let it sink the operation.\n }\n }, opts.warnAfterMs);\n if (typeof warnTimer.unref === 'function') warnTimer.unref();\n }\n\n timeoutTimer = setTimeout(() => {\n if (settled) return;\n settled = true;\n cleanup();\n const elapsed = Date.now() - startedAt;\n reject(opts.onTimeout(elapsed));\n }, opts.timeoutMs);\n if (typeof timeoutTimer.unref === 'function') timeoutTimer.unref();\n\n // Run the operation eagerly. If the timeout has already fired by the\n // time the operation settles, swallow the result silently — we have\n // already rejected the outer promise with the timeout error.\n Promise.resolve()\n .then(() => operation())\n .then(\n (value) => {\n if (settled) return;\n settled = true;\n cleanup();\n resolve(value);\n },\n (err) => {\n if (settled) return;\n settled = true;\n cleanup();\n reject(err);\n }\n );\n });\n}\n","import { getLogger } from '../utils/logger.js';\nimport { getLiveRenderer } from '../utils/live-renderer.js';\nimport { ProvisioningError, ResourceTimeoutError } from '../utils/error-handler.js';\nimport { withStackName, applyDefaultNameForFallback } from '../provisioning/resource-name.js';\nimport { IntrinsicFunctionResolver } from './intrinsic-function-resolver.js';\nimport { DagExecutor } from './dag-executor.js';\nimport type { CloudFormationTemplate, ResourceProvider } from '../types/resource.js';\nimport {\n STATE_SCHEMA_VERSION_CURRENT,\n shouldRetainResource,\n type StackState,\n type StateImportEntry,\n type ResourceState,\n type ResourceChange,\n} from '../types/state.js';\nimport type { S3StateBackend } from '../state/s3-state-backend.js';\nimport type { LockManager } from '../state/lock-manager.js';\nimport type { ExportIndexStore } from '../state/export-index-store.js';\nimport type { DagBuilder } from '../analyzer/dag-builder.js';\nimport type { DiffCalculator } from '../analyzer/diff-calculator.js';\nimport { ProviderRegistry } from '../provisioning/provider-registry.js';\nimport { CloudControlProvider } from '../provisioning/cloud-control-provider.js';\nimport { TemplateParser } from '../analyzer/template-parser.js';\nimport { IMPLICIT_DELETE_DEPENDENCIES } from '../analyzer/implicit-delete-deps.js';\nimport { withRetry } from './retry.js';\nimport { withResourceDeadline } from './resource-deadline.js';\n\n/**\n * Completed operation record for rollback tracking\n */\ninterface CompletedOperation {\n /** Logical ID of the resource */\n logicalId: string;\n /** Type of change that was applied */\n changeType: 'CREATE' | 'UPDATE' | 'DELETE';\n /** Resource type (e.g., \"AWS::S3::Bucket\") */\n resourceType: string;\n /** Previous resource state (for UPDATE rollback) */\n previousState?: ResourceState | undefined;\n /** Physical ID of newly created resource (for CREATE rollback) */\n physicalId?: string | undefined;\n /** Properties used for creation (for CREATE rollback / delete) */\n properties?: Record<string, unknown> | undefined;\n}\n\n/**\n * Default per-resource warn threshold: warn the user when a single\n * resource has been in flight for 5 minutes. Most CC API resources\n * complete in under a minute; 5m is the agreed elbow.\n */\nexport const DEFAULT_RESOURCE_WARN_AFTER_MS = 5 * 60 * 1000;\n\n/**\n * Default per-resource hard timeout: abort after 30 minutes. Matches the\n * design doc — Custom-Resource-heavy stacks should pass `--resource-timeout 1h`\n * explicitly because the Custom Resource provider's polling cap is 1h.\n */\nexport const DEFAULT_RESOURCE_TIMEOUT_MS = 30 * 60 * 1000;\n\n/**\n * Deploy engine options\n */\nexport interface DeployEngineOptions {\n /** Maximum concurrent resource operations */\n concurrency?: number;\n /** Dry run mode (plan only, no actual changes) */\n dryRun?: boolean;\n /** Lock timeout in milliseconds */\n lockTimeout?: number;\n /** User-provided parameter values */\n parameters?: Record<string, string>;\n /** Skip rollback on failure (save partial state and fail) */\n noRollback?: boolean;\n /**\n * Per-resource warn threshold (ms). When a single CREATE / UPDATE /\n * DELETE has been running this long, the live renderer's task label\n * gets a \"[taking longer than expected, Nm+]\" suffix and a\n * `logger.warn` line is emitted. Defaults to\n * {@link DEFAULT_RESOURCE_WARN_AFTER_MS}.\n *\n * Per-type override via {@link resourceWarnAfterByType} wins for\n * matching resource types.\n */\n resourceWarnAfterMs?: number;\n /**\n * Per-resource hard timeout (ms). When a single resource exceeds this,\n * `ResourceTimeoutError` is thrown and the existing rollback path\n * runs. Defaults to {@link DEFAULT_RESOURCE_TIMEOUT_MS}.\n *\n * Per-type override via {@link resourceTimeoutByType} wins for\n * matching resource types.\n */\n resourceTimeoutMs?: number;\n /**\n * Per-resource-type warn-after override map. Keys are\n * `AWS::Service::Resource` strings; values are milliseconds. When the\n * resource being provisioned matches a key here, that value supersedes\n * `resourceWarnAfterMs` at the call site.\n */\n resourceWarnAfterByType?: Record<string, number>;\n /**\n * Per-resource-type hard-timeout override map. Same shape as\n * {@link resourceWarnAfterByType}; supersedes `resourceTimeoutMs` at\n * the call site for matching types.\n */\n resourceTimeoutByType?: Record<string, number>;\n /**\n * When true, kick off `provider.readCurrentState` immediately after\n * each successful create / update so its result lands in\n * `ResourceState.observedProperties` for the drift comparator. Calls\n * are fire-and-forget — the deploy critical path does NOT block on\n * them — and a final `Promise.all` drains the in-flight set right\n * before the success state save.\n *\n * Defaults to `true`. Pass `--no-capture-observed-state` (or set\n * `cdk.json context.cdkd.captureObservedState: false`) to disable\n * when deploy speed is more important than rich drift detection.\n */\n captureObservedState?: boolean;\n}\n\n/**\n * Deploy result\n */\nexport interface DeployResult {\n /** Stack name */\n stackName: string;\n /** Number of resources created */\n created: number;\n /** Number of resources updated */\n updated: number;\n /** Number of resources deleted */\n deleted: number;\n /** Number of resources unchanged */\n unchanged: number;\n /** Total deployment time in milliseconds */\n durationMs: number;\n /**\n * Resolved stack outputs keyed by the template-declared Output name\n * (Export.Name duplicates are filtered out). Populated on a real\n * deploy and on the no-change path; undefined under --dry-run.\n */\n outputs?: Record<string, unknown>;\n}\n\n/**\n * Deploy engine orchestrates the entire deployment process\n *\n * Responsibilities:\n * 1. Acquire stack lock\n * 2. Load current state\n * 3. Calculate diff\n * 4. Validate resource types\n * 5. Execute deployment in DAG order\n * 6. Save new state\n * 7. Release lock\n *\n * Rollback mechanism:\n * - Tracks completed operations during deployment\n * - On failure, rolls back in reverse order (best-effort)\n * - Supports --no-rollback flag to skip rollback (saves partial state and fails)\n * - CREATE → delete the newly created resource\n * - UPDATE → restore previous properties\n * - DELETE → cannot rollback (log warning)\n */\n/**\n * Error thrown when deployment is interrupted by SIGINT\n */\nclass InterruptedError extends Error {\n constructor() {\n super('Deployment interrupted by user (Ctrl+C)');\n this.name = 'InterruptedError';\n }\n}\n\nexport class DeployEngine {\n private logger = getLogger().child('DeployEngine');\n private resolver: IntrinsicFunctionResolver;\n private interrupted = false;\n\n /**\n * In-flight `provider.readCurrentState` promises kicked off after a\n * successful CREATE / UPDATE. The deploy critical path does NOT\n * `await` these; instead they're drained at the end of `doDeploy`\n * (success path only) and the resolved values are merged into\n * `ResourceState.observedProperties` before the final state save.\n *\n * Each Promise resolves to the AWS-current snapshot, or `undefined`\n * if the provider does not implement `readCurrentState` or the call\n * threw — never rejects, so an unhandled-rejection cannot escape.\n */\n private observedCaptureTasks: Map<string, Promise<Record<string, unknown> | undefined>> =\n new Map();\n private stateBackend: S3StateBackend;\n private lockManager: LockManager;\n private dagBuilder: DagBuilder;\n private diffCalculator: DiffCalculator;\n private providerRegistry: ProviderRegistry;\n private options: DeployEngineOptions;\n /**\n * Optional persistent exports index store. When supplied, all\n * `Fn::ImportValue` resolutions in this deploy session prefer the\n * O(1) index lookup over the per-stack state.json scan, and the\n * consumer's `state.imports` field is populated for destroy-time\n * strong-reference checks. Shared across DeployEngine instances in\n * a single `cdkd deploy --all` invocation so the in-memory cache\n * survives across stacks.\n */\n private exportIndexStore: ExportIndexStore | undefined;\n /**\n * Per-deploy-session bag the resolver pushes resolved\n * `Fn::ImportValue` entries into. Reset at the start of each\n * `deploy()` call and persisted to `newState.imports` at the end.\n */\n private recordedImports: StateImportEntry[] = [];\n\n /**\n * Target region for this stack. Required — load-bearing for the\n * region-prefixed S3 state key and recorded in state.json for\n * cross-region destroy.\n */\n private stackRegion: string;\n\n constructor(\n stateBackend: S3StateBackend,\n lockManager: LockManager,\n dagBuilder: DagBuilder,\n diffCalculator: DiffCalculator,\n providerRegistry: ProviderRegistry,\n options: DeployEngineOptions = {},\n stackRegion: string,\n exportIndexStore?: ExportIndexStore\n ) {\n this.stateBackend = stateBackend;\n this.lockManager = lockManager;\n this.dagBuilder = dagBuilder;\n this.diffCalculator = diffCalculator;\n this.providerRegistry = providerRegistry;\n this.options = options;\n this.stackRegion = stackRegion;\n this.exportIndexStore = exportIndexStore;\n this.resolver = new IntrinsicFunctionResolver(stackRegion);\n this.options.concurrency = options.concurrency ?? 10;\n this.options.dryRun = options.dryRun ?? false;\n this.options.lockTimeout = options.lockTimeout ?? 5 * 60 * 1000; // 5 minutes\n this.options.noRollback = options.noRollback ?? false;\n this.options.resourceWarnAfterMs =\n options.resourceWarnAfterMs ?? DEFAULT_RESOURCE_WARN_AFTER_MS;\n this.options.resourceTimeoutMs = options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;\n // Default ON: drift detection without observedProperties is the\n // pre-PR behavior and we want the upgrade to be a strict superset.\n // The opt-out exists for users who care more about deploy speed\n // than the +0-10% drift-baseline overhead.\n this.options.captureObservedState = options.captureObservedState ?? true;\n }\n\n /**\n * Deploy a CloudFormation template\n */\n async deploy(stackName: string, template: CloudFormationTemplate): Promise<DeployResult> {\n // Reset per-session state. `recordedImports` is the bag the\n // resolver pushes Fn::ImportValue resolutions into; it lands in\n // `state.imports` at deploy save time.\n this.recordedImports = [];\n // Scope `stackName` to this deploy's async chain so concurrent\n // deploys (--stack-concurrency > 1) don't see each other's value.\n // See `src/provisioning/resource-name.ts` for the AsyncLocalStorage\n // background.\n return withStackName(stackName, () => this.doDeploy(stackName, template));\n }\n\n /**\n * Resolver context with the imports-recording and exports-index\n * fields wired in. Keeps the four+ inline context construction\n * sites consistent — pass through callable as\n * `this.buildResolverContext({...}, stackName)`.\n */\n private buildResolverContext(\n base: {\n template: CloudFormationTemplate;\n resources: Record<string, ResourceState>;\n parameters?: Record<string, unknown>;\n conditions?: Record<string, boolean>;\n },\n stackName: string\n ): import('./intrinsic-function-resolver.js').ResolverContext {\n return {\n template: base.template,\n resources: base.resources,\n ...(base.parameters &&\n Object.keys(base.parameters).length > 0 && { parameters: base.parameters }),\n ...(base.conditions &&\n Object.keys(base.conditions).length > 0 && { conditions: base.conditions }),\n stateBackend: this.stateBackend,\n stackName,\n ...(this.exportIndexStore && { exportIndex: this.exportIndexStore }),\n recordedImports: this.recordedImports,\n };\n }\n\n /**\n * Kick off `provider.readCurrentState` for a freshly-created/updated\n * resource without blocking the deploy critical path. The promise\n * lands in `observedCaptureTasks` keyed by `logicalId`; the deploy's\n * success-path drain (`drainObservedCaptures`) awaits the full set\n * and merges the resolved values into `ResourceState.observedProperties`\n * before the final state save.\n *\n * Errors are swallowed at the Promise level — readCurrentState\n * failing must not fail the deploy. The map entry resolves to\n * `undefined` for failures and for providers without\n * `readCurrentState`; both translate to \"no observedProperties\" at\n * the merge step, which is fine: drift falls back to comparing\n * against `properties`.\n */\n private kickOffObservedCapture(\n provider: ResourceProvider,\n logicalId: string,\n physicalId: string,\n resourceType: string,\n resolvedProps: Record<string, unknown>,\n context?: import('../types/resource.js').ReadCurrentStateContext\n ): void {\n if (this.options.captureObservedState !== true) return;\n if (!provider.readCurrentState) return;\n\n const promise = provider\n .readCurrentState(physicalId, logicalId, resourceType, resolvedProps, context)\n .catch((err: unknown) => {\n this.logger.debug(\n `observedProperties capture for ${logicalId} (${resourceType}) failed: ${err instanceof Error ? err.message : String(err)} — drift will fall back to template properties for this resource until the next successful deploy.`\n );\n return undefined;\n });\n this.observedCaptureTasks.set(logicalId, promise);\n }\n\n /**\n * Wait for every in-flight `readCurrentState` promise from the\n * deploy's success path, then merge each resolved snapshot into the\n * matching `ResourceState.observedProperties`. After this runs the\n * map is drained so a subsequent deploy starts fresh.\n *\n * Called from `doDeploy` immediately before the final `saveState`.\n * The rollback / failure paths intentionally do NOT call this — a\n * failed deploy's partial state is already inconsistent, and waiting\n * on potentially many in-flight reads would slow down the rollback\n * itself.\n */\n private async drainObservedCaptures(\n stateResources: Record<string, ResourceState>\n ): Promise<void> {\n if (this.observedCaptureTasks.size === 0) return;\n const entries = Array.from(this.observedCaptureTasks.entries());\n this.observedCaptureTasks.clear();\n const resolved = await Promise.all(entries.map(([, p]) => p));\n for (let i = 0; i < entries.length; i++) {\n const logicalId = entries[i]![0];\n const observed = resolved[i];\n const target = stateResources[logicalId];\n if (target && observed !== undefined) {\n target.observedProperties = observed;\n }\n }\n }\n\n /**\n * Kick off `provider.readCurrentState` for every resource in the\n * loaded state that lacks `observedProperties` (e.g. state written\n * by a pre-v3 binary, or a v3 record where a NO_CHANGE-skipped\n * resource's baseline never landed). Calls go through\n * `kickOffObservedCapture`, so they share the same fire-and-forget\n * pipeline, error swallowing, and final-drain wiring that the\n * post-CREATE / post-UPDATE captures use.\n *\n * The deploy critical path does NOT wait on these; the cost is\n * bounded by `max(per-resource readCurrentState latency)` (typically\n * ~200-300ms in practice) once at the end-of-deploy drain. Any\n * resource that subsequently goes through CREATE / UPDATE in the\n * same deploy will overwrite this entry via the `Map.set` keyed by\n * `logicalId` (latest-wins) — so there's no double-write to state,\n * just a wasted SDK call for the (rare) UPDATE / DELETE intersection.\n *\n * Resources whose provider lookup throws (e.g. unsupported type) or\n * lacks `readCurrentState` are silently skipped — same policy as the\n * manual `cdkd state refresh-observed` command.\n */\n private kickOffAutoRefreshObservedProperties(\n stateResources: Record<string, ResourceState>\n ): void {\n if (this.options.captureObservedState !== true) return;\n // Dry run must not fire real SDK reads (matches the dry-run\n // guarantee that no AWS side-effect runs).\n if (this.options.dryRun === true) return;\n let toRefresh = 0;\n const candidates: Array<{\n logicalId: string;\n resource: ResourceState;\n }> = [];\n for (const [logicalId, resource] of Object.entries(stateResources)) {\n if (resource.observedProperties !== undefined) continue;\n candidates.push({ logicalId, resource });\n }\n if (candidates.length === 0) return;\n\n // Issue #323: at the v2→v3 schema-upgrade refresh path, state is\n // fully loaded from the previous deploy — sibling AWS::IAM::Policy\n // resources are all present. Pass a cross-resource context so IAM\n // providers can filter inline policies managed via sibling\n // resources, otherwise observed.Policies would record the\n // sibling-managed entries and the next `cdkd drift` would fire\n // false drift (filtered AWS-current = []) until `cdkd drift\n // --accept` runs. Build the siblings map once and clone-minus-self\n // per resource to avoid an O(N²) walk.\n const allSiblings: Record<\n string,\n { resourceType: string; properties: Record<string, unknown> }\n > = {};\n for (const [lid, res] of Object.entries(stateResources)) {\n allSiblings[lid] = {\n resourceType: res.resourceType,\n properties: res.properties ?? {},\n };\n }\n\n for (const { logicalId, resource } of candidates) {\n // Skip-list / unsupported types: getProvider throws — silently skip\n // (mirrors `cdkd state refresh-observed`'s policy: best-effort,\n // no failure on a state record we cannot resolve).\n let provider: ResourceProvider;\n try {\n provider = this.providerRegistry.getProvider(resource.resourceType);\n } catch {\n continue;\n }\n if (!provider.readCurrentState) continue;\n const siblings = { ...allSiblings };\n delete siblings[logicalId];\n this.kickOffObservedCapture(\n provider,\n logicalId,\n resource.physicalId,\n resource.resourceType,\n resource.properties ?? {},\n { siblings }\n );\n toRefresh++;\n }\n\n if (toRefresh > 0) {\n this.logger.warn(\n `cdkd state schema upgrade detected — refreshing observed-properties baseline for ${toRefresh} resource(s) (one-time, runs in parallel with deploy)`\n );\n }\n }\n\n private async doDeploy(\n stackName: string,\n template: CloudFormationTemplate\n ): Promise<DeployResult> {\n const startTime = Date.now();\n this.logger.debug(`Starting deployment for stack: ${stackName}`);\n\n // Acquire lock with retry (retries up to 3 times with 2s delay for transient lock conflicts)\n await this.lockManager.acquireLockWithRetry(stackName, this.stackRegion, undefined, 'deploy');\n\n // Live progress renderer: shows in-flight resources as a multi-line area\n // at the bottom of the terminal. Self-disables on non-TTY and when\n // `CDKD_NO_LIVE=1` is set (the CLI sets this in verbose mode so debug\n // logs do not interleave with the live area).\n const renderer = getLiveRenderer();\n renderer.start();\n\n // Register SIGINT handler to save partial state on Ctrl+C\n this.interrupted = false;\n const sigintHandler = () => {\n // Route the interrupt notice through the live renderer so it does not\n // collide with the in-flight task display.\n renderer.printAbove(() => {\n process.stderr.write(\n '\\nInterrupted — saving partial state after current operations complete...\\n'\n );\n });\n this.interrupted = true;\n };\n process.on('SIGINT', sigintHandler);\n\n try {\n // 1. Load current state\n const currentStateData = await this.stateBackend.getState(stackName, this.stackRegion);\n const currentState: StackState = currentStateData?.state ?? {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName,\n resources: {},\n outputs: {},\n lastModified: Date.now(),\n };\n const currentEtag = currentStateData?.etag;\n // Set when we loaded a `version: 1` legacy record. The next save\n // migrates it to the new key.\n const migrationPending = currentStateData?.migrationPending ?? false;\n\n this.logger.debug(\n `Loaded current state: ${Object.keys(currentState.resources).length} resources`\n );\n\n // 1a. Auto-refresh observedProperties for any state entry that lacks it\n // (state written by an older binary / direct edit). Fires\n // `provider.readCurrentState` fire-and-forget through the same\n // `kickOffObservedCapture` pipeline that successful CREATE / UPDATE\n // uses, so the in-flight set is drained right before the final\n // `saveState`. Latest-wins semantics (Map.set keyed by logicalId)\n // means a CREATE / UPDATE later in the same deploy overwrites\n // the auto-refresh entry — no double-write to state. CREATEs for\n // brand-new resources skip this loop because they're not yet in\n // `currentState.resources`. Closes the upgrade UX gap left by\n // v3 schema: the manual `cdkd state refresh-observed` command\n // remains for non-deploy refresh.\n this.kickOffAutoRefreshObservedProperties(currentState.resources);\n\n // 2. Template parsing is handled by DagBuilder (dependency analysis) and\n // IntrinsicResolver (intrinsic function resolution) in later steps\n this.logger.debug(`Template has ${Object.keys(template.Resources || {}).length} resources`);\n\n // 2.5. Resolve parameters from template and user input\n const parameterValues = await this.resolver.resolveParameters(\n template,\n this.options.parameters\n );\n this.logger.debug(\n `Resolved ${Object.keys(parameterValues).length} parameters: ${Object.keys(parameterValues).join(', ')}`\n );\n\n // 2.6. Evaluate conditions from template\n const context = this.buildResolverContext(\n {\n template,\n resources: currentState.resources,\n parameters: parameterValues,\n },\n stackName\n );\n const conditions = await this.resolver.evaluateConditions(context);\n this.logger.debug(\n `Evaluated ${Object.keys(conditions).length} conditions: ${Object.keys(conditions).join(', ')}`\n );\n\n // 3. Validate resource types (before deployment starts)\n // Skip metadata resources as they don't actually deploy\n const resourceTypes = new Set(\n Object.values(template.Resources || {})\n .map((r) => r.Type)\n .filter((type) => type !== 'AWS::CDK::Metadata')\n );\n this.providerRegistry.validateResourceTypes(resourceTypes);\n this.logger.debug(`All resource types validated`);\n\n // 4. Build dependency graph\n const dag = this.dagBuilder.buildGraph(template);\n const executionLevels = this.dagBuilder.getExecutionLevels(dag);\n this.logger.debug(`Dependency graph: ${executionLevels.length} execution levels`);\n\n // 5. Calculate diff\n // Pass a best-effort resolver so that changes hidden inside intrinsics (e.g.\n // `Fn::Join` literal args like \"-value\" -> \"-value2\") are detected against\n // the already-resolved values stored in state.\n const diffResolverContext = this.buildResolverContext(\n {\n template,\n resources: currentState.resources,\n parameters: parameterValues,\n conditions,\n },\n stackName\n );\n const diffResolveFn = (value: unknown) => this.resolver.resolve(value, diffResolverContext);\n const changes = await this.diffCalculator.calculateDiff(\n currentState,\n template,\n diffResolveFn\n );\n const hasChanges = this.diffCalculator.hasChanges(changes);\n\n if (!hasChanges) {\n this.logger.info('No changes detected. Stack is up to date.');\n\n // No-change path: if the auto-refresh kicked off any\n // readCurrentState calls (e.g. v2 → v3 schema upgrade on a\n // stack with nothing to deploy), drain them and persist the\n // refreshed observed-properties baseline so the next `cdkd\n // drift` run sees a real AWS-current snapshot. Skipped in\n // dry-run / when nothing was kicked off (drainObservedCaptures\n // short-circuits on empty map).\n if (!this.options.dryRun && this.observedCaptureTasks.size > 0) {\n await this.drainObservedCaptures(currentState.resources);\n try {\n const refreshedState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: currentState.resources,\n outputs: currentState.outputs,\n // Preserve existing imports[] (no-change path: nothing\n // re-resolved). Otherwise the refresh would silently\n // strip the strong-reference record on every diff-clean\n // deploy.\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n const saveOptions: { expectedEtag?: string; migrateLegacy?: boolean } = {};\n if (currentEtag !== undefined) saveOptions.expectedEtag = currentEtag;\n if (migrationPending) saveOptions.migrateLegacy = true;\n await this.stateBackend.saveState(\n stackName,\n this.stackRegion,\n refreshedState,\n saveOptions\n );\n this.logger.debug('Persisted refreshed observedProperties (no-change path)');\n } catch (saveError) {\n this.logger.warn(\n `Failed to persist refreshed observedProperties: ${saveError instanceof Error ? saveError.message : String(saveError)} — drift baseline will be re-fetched on next deploy.`\n );\n }\n }\n\n return {\n stackName,\n created: 0,\n updated: 0,\n deleted: 0,\n unchanged: Object.keys(currentState.resources).length,\n durationMs: Date.now() - startTime,\n outputs: this.buildDisplayOutputs(template, currentState.outputs ?? {}),\n };\n }\n\n // Log changes summary\n const createChanges = this.diffCalculator.filterByType(changes, 'CREATE');\n const updateChanges = this.diffCalculator.filterByType(changes, 'UPDATE');\n const deleteChanges = this.diffCalculator.filterByType(changes, 'DELETE');\n\n this.logger.info(\n `Changes: ${createChanges.length} to create, ${updateChanges.length} to update, ${deleteChanges.length} to delete`\n );\n\n if (this.options.dryRun) {\n this.logger.info('Dry run mode - skipping actual deployment');\n return {\n stackName,\n created: createChanges.length,\n updated: updateChanges.length,\n deleted: deleteChanges.length,\n unchanged: this.diffCalculator.filterByType(changes, 'NO_CHANGE').length,\n durationMs: Date.now() - startTime,\n };\n }\n\n // Progress counter for tracking overall deployment progress\n const totalOperations = createChanges.length + updateChanges.length + deleteChanges.length;\n const progress = { current: 0, total: totalOperations };\n\n // 6. Execute deployment (event-driven DAG dispatch with partial state saves)\n const { state: newState, actualCounts } = await this.executeDeployment(\n template,\n currentState,\n changes,\n dag,\n executionLevels,\n stackName,\n parameterValues,\n conditions,\n currentEtag,\n progress,\n migrationPending\n );\n\n // 7a. Drain in-flight readCurrentState promises so each resource's\n // observedProperties lands in newState before we persist it. By\n // this point the deploy critical path is over, so awaiting the\n // remaining captures only adds the longest still-pending read\n // (typically <300ms in practice for medium stacks; see PR notes).\n await this.drainObservedCaptures(newState.resources);\n\n // 7b. Save final state (ETag may have been updated by partial saves).\n // The legacy migration delete (when migrationPending) was already done by\n // the first per-resource save inside executeDeployment, so this final\n // save is unconditionally region-scoped.\n const newEtag = await this.stateBackend.saveState(stackName, this.stackRegion, newState);\n this.logger.debug(`State saved (ETag: ${newEtag})`);\n\n // 7c. Update the persistent exports index with this stack's\n // outputs so subsequent `Fn::ImportValue` resolves hit O(1).\n // Best-effort: failures are swallowed inside updateForStack and\n // surfaced as warnings (state.json is canonical; a stale index\n // self-heals on the next deploy/resolve fallback).\n if (this.exportIndexStore) {\n await this.exportIndexStore.updateForStack(\n stackName,\n this.stackRegion,\n (newState.outputs as Record<string, unknown>) ?? {}\n );\n }\n\n const durationMs = Date.now() - startTime;\n const unchangedCount =\n this.diffCalculator.filterByType(changes, 'NO_CHANGE').length + actualCounts.skipped;\n\n return {\n stackName,\n created: actualCounts.created,\n updated: actualCounts.updated,\n deleted: actualCounts.deleted,\n unchanged: unchangedCount,\n durationMs,\n outputs: this.buildDisplayOutputs(template, newState.outputs ?? {}),\n };\n } finally {\n // Stop live renderer (clears any remaining in-flight task display)\n renderer.stop();\n\n // Remove SIGINT handler\n process.removeListener('SIGINT', sigintHandler);\n\n // On a rollback / SIGINT exit we may leave in-flight readCurrentState\n // promises in the map (the success path drains them above). Clear the\n // map so a re-used engine instance does not accumulate stale entries\n // across deploys. The underlying promises already have a `.catch` so\n // dropping the references will not produce an unhandled rejection.\n this.observedCaptureTasks.clear();\n\n // Always release lock\n try {\n await this.lockManager.releaseLock(stackName, this.stackRegion);\n this.logger.debug('Lock released');\n } catch (lockError) {\n this.logger.warn(\n `Failed to release lock: ${lockError instanceof Error ? lockError.message : String(lockError)}`\n );\n }\n }\n }\n\n /**\n * Execute deployment by processing resources via event-driven DAG dispatch.\n *\n * - CREATE/UPDATE follow forward dependency order (a node starts as soon as\n * ALL of its dependencies are completed — does not wait for unrelated\n * siblings in the same \"level\")\n * - DELETE follows reverse dependency order (a node starts as soon as all\n * resources that depend ON it have finished deleting)\n */\n private async executeDeployment(\n template: CloudFormationTemplate,\n currentState: StackState,\n changes: Map<string, ResourceChange>,\n dag: ReturnType<DagBuilder['buildGraph']>,\n executionLevels: string[][],\n stackName: string,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>,\n currentEtag?: string,\n progress?: { current: number; total: number },\n migrationPending = false\n ): Promise<{\n state: StackState;\n actualCounts: { created: number; updated: number; deleted: number; skipped: number };\n }> {\n const concurrency = this.options.concurrency!;\n const newResources: Record<string, ResourceState> = { ...currentState.resources };\n const actualCounts = { created: 0, updated: 0, deleted: 0, skipped: 0 };\n const completedOperations: CompletedOperation[] = [];\n // Tracked here so the FIRST per-resource save sweeps the legacy key; we\n // don't want to delete it on every save.\n let pendingMigration = migrationPending;\n\n // Serialize per-resource state saves to avoid ETag conflicts from concurrent writes\n let saveChain: Promise<void> = Promise.resolve();\n const saveStateAfterResource = (logicalId: string): void => {\n if (currentEtag === undefined) return;\n saveChain = saveChain.then(async () => {\n try {\n const partialState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n // Per-resource partial save: imports[] reverts to the\n // pre-deploy snapshot. recordedImports from this session\n // are persisted only on the final success path.\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n // Migration is a one-shot tail on the first save; subsequent saves\n // overwrite the new key in-place under optimistic locking.\n const migrate = pendingMigration;\n const expectedEtag = migrate ? undefined : currentEtag;\n currentEtag = await this.stateBackend.saveState(\n stackName,\n this.stackRegion,\n partialState,\n { ...(expectedEtag !== undefined && { expectedEtag }), migrateLegacy: migrate }\n );\n if (migrate) pendingMigration = false;\n this.logger.debug(`State saved after ${logicalId}`);\n } catch (error) {\n this.logger.warn(\n `Failed to save state after ${logicalId}: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n });\n };\n\n // Separate DELETE operations from CREATE/UPDATE\n const deleteChanges = new Set(\n Array.from(changes.entries())\n .filter(([_, change]) => change.changeType === 'DELETE')\n .map(([logicalId]) => logicalId)\n );\n\n try {\n // Step 1: Process CREATE/UPDATE via event-driven DAG dispatch.\n // A node starts as soon as ALL of its dependencies are completed, rather\n // than waiting for an entire \"level\" of unrelated siblings to finish.\n const createUpdateIds: string[] = [];\n for (const [id, change] of changes.entries()) {\n if (deleteChanges.has(id)) continue;\n if (change.changeType === 'NO_CHANGE') continue;\n createUpdateIds.push(id);\n }\n\n if (createUpdateIds.length > 0) {\n this.logger.info(\n `Deploying ${createUpdateIds.length} resource(s) (DAG: ${executionLevels.length} levels, max parallel: ${concurrency})`\n );\n\n const createUpdateExecutor = new DagExecutor<ResourceChange>();\n const provisionable = new Set(createUpdateIds);\n for (const id of createUpdateIds) {\n const allDeps = this.dagBuilder.getDirectDependencies(dag, id);\n // Only carry deps that are themselves being provisioned in this phase;\n // NO_CHANGE / DELETE / non-DAG deps are already satisfied.\n const deps = new Set(allDeps.filter((d) => provisionable.has(d)));\n createUpdateExecutor.add({\n id,\n dependencies: deps,\n state: 'pending',\n data: changes.get(id)!,\n });\n }\n\n try {\n await createUpdateExecutor.execute(\n concurrency,\n async (node) => {\n const logicalId = node.id;\n const change = node.data;\n\n const previousState = currentState.resources[logicalId]\n ? { ...currentState.resources[logicalId] }\n : undefined;\n\n try {\n await this.provisionResource(\n logicalId,\n change,\n newResources,\n stackName,\n template,\n parameterValues,\n conditions,\n actualCounts,\n progress\n );\n } catch (provisionError) {\n // Signal interruption so that long-running operations (e.g., CloudFront\n // waitForDeployed) in sibling tasks abort promptly instead of blocking\n // until their own polling timeouts fire.\n this.interrupted = true;\n throw provisionError;\n }\n\n completedOperations.push({\n logicalId,\n changeType: change.changeType as 'CREATE' | 'UPDATE',\n resourceType: change.resourceType,\n previousState,\n physicalId: newResources[logicalId]?.physicalId,\n properties: newResources[logicalId]?.properties,\n });\n\n saveStateAfterResource(logicalId);\n },\n () => this.interrupted\n );\n } finally {\n // Wait for any pending per-resource state saves before the next phase or\n // before propagating an error — prevents partial-save races.\n await saveChain;\n }\n\n // If SIGINT fired AND there is still un-provisioned work (some nodes\n // remained pending because dispatch was cancelled), surface it as an\n // explicit interruption so the catch path saves partial state.\n // If every node already completed before SIGINT landed, treat the deploy\n // as fully successful — matches the prior level-loop's \"loop exits, no\n // check\" behaviour at the very end of execution.\n if (this.interrupted && this.hasPending(createUpdateExecutor)) {\n throw new InterruptedError();\n }\n }\n\n // Step 2: Process DELETE operations in reverse dependency order.\n if (deleteChanges.size > 0) {\n this.logger.info(`Deleting ${deleteChanges.size} resource(s)`);\n\n const deleteDeps = this.buildDeletionDependencies(deleteChanges, currentState);\n const deleteExecutor = new DagExecutor<ResourceChange>();\n for (const id of deleteChanges) {\n deleteExecutor.add({\n id,\n dependencies: deleteDeps.get(id) ?? new Set(),\n state: 'pending',\n data: changes.get(id)!,\n });\n }\n\n try {\n await deleteExecutor.execute(\n concurrency,\n async (node) => {\n const logicalId = node.id;\n const change = node.data;\n\n const previousState = currentState.resources[logicalId]\n ? { ...currentState.resources[logicalId] }\n : undefined;\n\n try {\n await this.provisionResource(\n logicalId,\n change,\n newResources,\n stackName,\n template,\n parameterValues,\n conditions,\n actualCounts,\n progress\n );\n } catch (provisionError) {\n this.interrupted = true;\n throw provisionError;\n }\n\n completedOperations.push({\n logicalId,\n changeType: 'DELETE',\n resourceType: change.resourceType,\n previousState,\n });\n\n saveStateAfterResource(logicalId);\n },\n () => this.interrupted\n );\n } finally {\n await saveChain;\n }\n\n if (this.interrupted && this.hasPending(deleteExecutor)) {\n throw new InterruptedError();\n }\n }\n } catch (error) {\n // Save partial state BEFORE rollback to track all successfully provisioned\n // resources (including those that completed concurrently with the one that\n // failed). This prevents orphaned resources — resources that exist in AWS\n // but not in the state file.\n try {\n const preRollbackState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n const migrate = pendingMigration;\n const expectedEtag = migrate ? undefined : currentEtag;\n currentEtag = await this.stateBackend.saveState(\n stackName,\n this.stackRegion,\n preRollbackState,\n { ...(expectedEtag !== undefined && { expectedEtag }), migrateLegacy: migrate }\n );\n if (migrate) pendingMigration = false;\n this.logger.debug('Partial state saved before rollback (orphaned resource tracking)');\n } catch (saveError) {\n this.logger.warn(\n `Failed to save partial state before rollback: ${saveError instanceof Error ? saveError.message : String(saveError)}`\n );\n }\n\n // On SIGINT, skip rollback — just save partial state and let the caller exit\n if (error instanceof InterruptedError) {\n this.logger.info(\n `Partial state saved (${Object.keys(newResources).length} resources). ` +\n 'Run deploy again to resume, or destroy to clean up.'\n );\n throw error;\n }\n\n // Deployment failed — attempt rollback unless --no-rollback is set\n if (this.options.noRollback) {\n this.logger.warn('Deployment failed. --no-rollback is set, skipping rollback.');\n this.logger.warn('Partial state has been saved. Manual cleanup may be required.');\n } else {\n await this.performRollback(completedOperations, newResources, stackName);\n }\n\n // Save state after rollback (reflects rolled-back resource state).\n // This is critical: if rollback deleted resources, the state must reflect\n // that. Otherwise, next deploy will think deleted resources still exist.\n try {\n const postRollbackState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n await this.stateBackend.saveState(stackName, this.stackRegion, postRollbackState, {\n ...(currentEtag !== undefined && { expectedEtag: currentEtag }),\n });\n this.logger.debug('State saved after deployment failure');\n } catch (saveError) {\n // ETag mismatch from per-resource saves — force overwrite with fresh ETag\n this.logger.debug(\n `Retrying state save after rollback (ETag mismatch): ${saveError instanceof Error ? saveError.message : String(saveError)}`\n );\n try {\n const freshState = await this.stateBackend.getState(stackName, this.stackRegion);\n const freshEtag = freshState?.etag;\n const postRollbackState: StackState = {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs: currentState.outputs,\n ...(currentState.imports &&\n currentState.imports.length > 0 && {\n imports: currentState.imports,\n }),\n lastModified: Date.now(),\n };\n await this.stateBackend.saveState(stackName, this.stackRegion, postRollbackState, {\n ...(freshEtag !== undefined && { expectedEtag: freshEtag }),\n });\n this.logger.debug('State saved after deployment failure (retry succeeded)');\n } catch (retryError) {\n this.logger.warn(\n `Failed to save state after rollback: ${retryError instanceof Error ? retryError.message : String(retryError)}`\n );\n }\n }\n\n throw error;\n }\n\n // Resolve outputs\n const outputs = await this.resolveOutputs(\n template,\n newResources,\n stackName,\n parameterValues,\n conditions\n );\n\n return {\n state: {\n version: STATE_SCHEMA_VERSION_CURRENT,\n region: this.stackRegion,\n stackName: currentState.stackName,\n resources: newResources,\n outputs,\n ...(this.recordedImports.length > 0 && { imports: [...this.recordedImports] }),\n lastModified: Date.now(),\n },\n actualCounts,\n };\n }\n\n /**\n * Perform best-effort rollback of completed operations respecting dependencies\n *\n * - CREATE → delete the newly created resource (in reverse dependency order)\n * - UPDATE → update back to previous properties\n * - DELETE → cannot rollback (resource already deleted), log warning\n *\n * Resources completed concurrently in the dispatcher may have dependencies\n * between them (e.g., IAM Policy depends on IAM Role). When rolling back\n * CREATEs (deleting), dependent resources must be deleted before their\n * dependencies. This method sorts CREATE rollback operations using dependency\n * information from state, then processes UPDATE/DELETE rollbacks, and finally\n * processes sorted CREATE rollback deletions.\n */\n private async performRollback(\n completedOperations: CompletedOperation[],\n stateResources: Record<string, ResourceState>,\n _stackName: string\n ): Promise<void> {\n if (completedOperations.length === 0) {\n this.logger.info('No completed operations to roll back.');\n return;\n }\n\n this.logger.info(`Rolling back ${completedOperations.length} completed operation(s)...`);\n\n // Separate CREATE operations (which need dependency-aware ordering) from others\n const createOps: CompletedOperation[] = [];\n const otherOps: CompletedOperation[] = [];\n\n for (const op of completedOperations) {\n if (op.changeType === 'CREATE') {\n createOps.push(op);\n } else {\n otherOps.push(op);\n }\n }\n\n // Step 1: Process UPDATE/DELETE rollbacks in reverse order (simple reversal is fine)\n for (let i = otherOps.length - 1; i >= 0; i--) {\n const op = otherOps[i]!;\n await this.performSingleRollback(op, stateResources);\n }\n\n // Step 2: Process CREATE rollbacks (deletions) in dependency-aware order\n // (reverse dependency: dependents are deleted before their dependencies)\n if (createOps.length > 0) {\n const sortedCreateOps = this.sortRollbackCreates(createOps, stateResources);\n for (const op of sortedCreateOps) {\n await this.performSingleRollback(op, stateResources);\n }\n }\n\n this.logger.info('Rollback completed. Some resources may remain if deletion failed.');\n }\n\n /**\n * Sort CREATE rollback operations so that resources depending on others\n * are deleted first (reverse dependency order).\n *\n * Uses state dependencies to determine reverse-dependency order, similar to buildDeletionDependencies.\n */\n private sortRollbackCreates(\n createOps: CompletedOperation[],\n stateResources: Record<string, ResourceState>\n ): CompletedOperation[] {\n const opMap = new Map<string, CompletedOperation>();\n const deleteIds = new Set<string>();\n for (const op of createOps) {\n opMap.set(op.logicalId, op);\n deleteIds.add(op.logicalId);\n }\n\n // Build reverse dependency map: resource → resources that depend on it\n const dependedBy = new Map<string, Set<string>>();\n for (const id of deleteIds) {\n if (!dependedBy.has(id)) dependedBy.set(id, new Set());\n }\n\n for (const id of deleteIds) {\n const resource = stateResources[id];\n if (!resource?.dependencies) continue;\n for (const dep of resource.dependencies) {\n if (!deleteIds.has(dep)) continue;\n // id depends on dep → dep must be deleted AFTER id\n if (!dependedBy.has(dep)) dependedBy.set(dep, new Set());\n dependedBy.get(dep)!.add(id);\n }\n }\n\n // Topological sort (Kahn's algorithm) — produces levels for parallel delete\n const sorted: CompletedOperation[] = [];\n let remaining = new Set(deleteIds);\n\n while (remaining.size > 0) {\n // Find resources with no remaining dependents (safe to delete now)\n const level: string[] = [];\n for (const id of remaining) {\n const dependents = dependedBy.get(id);\n const hasPendingDependents = dependents\n ? [...dependents].some((d) => remaining.has(d))\n : false;\n if (!hasPendingDependents) {\n level.push(id);\n }\n }\n\n if (level.length === 0) {\n // Circular dependency fallback: add all remaining\n this.logger.warn(\n `Circular dependency detected in rollback order, processing remaining ${remaining.size} resources`\n );\n for (const id of remaining) {\n const op = opMap.get(id);\n if (op) sorted.push(op);\n }\n break;\n }\n\n for (const id of level) {\n const op = opMap.get(id);\n if (op) sorted.push(op);\n }\n remaining = new Set([...remaining].filter((id) => !level.includes(id)));\n }\n\n this.logger.debug(\n `Rollback CREATE deletion order: ${sorted.map((op) => op.logicalId).join(' → ')}`\n );\n return sorted;\n }\n\n /**\n * Perform a single rollback operation (extracted for reuse)\n */\n private async performSingleRollback(\n op: CompletedOperation,\n stateResources: Record<string, ResourceState>\n ): Promise<void> {\n try {\n switch (op.changeType) {\n case 'CREATE': {\n // Rollback CREATE by deleting the newly created resource\n if (!op.physicalId) {\n this.logger.warn(` Rollback: Cannot delete ${op.logicalId} — no physical ID recorded`);\n break;\n }\n\n this.logger.info(\n ` Rollback: Deleting created resource ${op.logicalId} (${op.resourceType})`\n );\n const provider = this.providerRegistry.getProvider(op.resourceType);\n await provider.delete(op.logicalId, op.physicalId, op.resourceType, op.properties, {\n expectedRegion: this.stackRegion,\n });\n\n // Remove from state\n delete stateResources[op.logicalId];\n this.logger.info(` Rollback: ${op.logicalId} deleted successfully`);\n break;\n }\n\n case 'UPDATE': {\n // Rollback UPDATE by restoring previous properties\n if (!op.previousState) {\n this.logger.warn(\n ` Rollback: Cannot restore ${op.logicalId} — no previous state available`\n );\n break;\n }\n\n this.logger.info(\n ` Rollback: Restoring ${op.logicalId} (${op.resourceType}) to previous state`\n );\n const provider = this.providerRegistry.getProvider(op.resourceType);\n const currentResource = stateResources[op.logicalId];\n\n if (!currentResource) {\n this.logger.warn(\n ` Rollback: Cannot restore ${op.logicalId} — resource not found in current state`\n );\n break;\n }\n\n await provider.update(\n op.logicalId,\n currentResource.physicalId,\n op.resourceType,\n op.previousState.properties,\n currentResource.properties\n );\n\n // Restore previous state\n stateResources[op.logicalId] = op.previousState;\n this.logger.info(` Rollback: ${op.logicalId} restored successfully`);\n break;\n }\n\n case 'DELETE': {\n // Cannot rollback DELETE — resource is already deleted\n this.logger.warn(\n ` Rollback: Cannot restore deleted resource ${op.logicalId} (${op.resourceType}) — resource has already been deleted`\n );\n break;\n }\n }\n } catch (rollbackError) {\n // Best-effort: log warning and continue with remaining rollbacks\n this.logger.warn(\n ` Rollback failed for ${op.logicalId} (${op.changeType}): ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`\n );\n this.logger.warn(' Continuing with remaining rollback operations...');\n }\n }\n\n /**\n * Provision a single resource (CREATE/UPDATE/DELETE)\n */\n private async provisionResource(\n logicalId: string,\n change: ResourceChange,\n stateResources: Record<string, ResourceState>,\n stackName: string,\n template?: CloudFormationTemplate,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>,\n counts?: { created: number; updated: number; deleted: number; skipped: number },\n progress?: { current: number; total: number }\n ): Promise<void> {\n const resourceType = change.resourceType;\n\n const renderer = getLiveRenderer();\n const needsReplacement =\n change.changeType === 'UPDATE' &&\n (change.propertyChanges?.some((pc) => pc.requiresReplacement) ?? false);\n const verb =\n change.changeType === 'CREATE'\n ? 'Creating'\n : change.changeType === 'DELETE'\n ? 'Deleting'\n : needsReplacement\n ? 'Replacing'\n : 'Updating';\n const baseLabel = `${verb} ${logicalId} (${resourceType})`;\n renderer.addTask(logicalId, baseLabel);\n\n // Operation classification for the timeout error message. UPDATE and\n // its replacement-replacement form are both surfaced as 'UPDATE' since\n // the user-facing distinction (which immutable property triggered it)\n // is already in the renderer label.\n const operationKind: 'CREATE' | 'UPDATE' | 'DELETE' =\n change.changeType === 'CREATE'\n ? 'CREATE'\n : change.changeType === 'DELETE'\n ? 'DELETE'\n : 'UPDATE';\n\n // Per-resource-type overrides (v2) win over the global default.\n // Resolution order at the call site:\n // 1. per-type CLI override map for this resourceType — explicit\n // escape hatch, always wins (`--resource-timeout TYPE=DURATION`).\n // 2. provider self-report (`getMinResourceTimeoutMs()`) raised\n // against the global default — long-running providers\n // (Custom Resource polls up to 1h) lift the deadline for their\n // resources without forcing every user to remember\n // `--resource-timeout 1h`.\n // 3. CLI global default (`--resource-timeout 30m`).\n // 4. compile-time default (DEFAULT_RESOURCE_*_MS).\n const provider = this.providerRegistry.getProvider(resourceType);\n const providerMinTimeoutMs = provider.getMinResourceTimeoutMs?.() ?? 0;\n const warnAfterMs =\n this.options.resourceWarnAfterByType?.[resourceType] ??\n this.options.resourceWarnAfterMs ??\n DEFAULT_RESOURCE_WARN_AFTER_MS;\n const globalTimeoutMs = this.options.resourceTimeoutMs ?? DEFAULT_RESOURCE_TIMEOUT_MS;\n const timeoutMs =\n this.options.resourceTimeoutByType?.[resourceType] ??\n Math.max(providerMinTimeoutMs, globalTimeoutMs);\n\n try {\n await withResourceDeadline(\n async () => {\n await this.provisionResourceBody(\n logicalId,\n change,\n stateResources,\n stackName,\n template,\n parameterValues,\n conditions,\n counts,\n progress\n );\n },\n {\n warnAfterMs,\n timeoutMs,\n onWarn: (elapsedMs) => {\n const minutes = Math.max(1, Math.round(elapsedMs / 60_000));\n const warnSuffix = ` [taking longer than expected, ${minutes}m+]`;\n // Mutate the live renderer's task label in place (TTY mode)\n // and emit a warn line above the live area (non-TTY / verbose).\n renderer.updateTaskLabel(logicalId, `${baseLabel}${warnSuffix}`);\n renderer.printAbove(() => {\n this.logger.warn(\n `${logicalId} (${resourceType}) has been ${operationKind === 'CREATE' ? 'creating' : operationKind === 'DELETE' ? 'deleting' : 'updating'} for ${minutes}m — still waiting`\n );\n });\n },\n onTimeout: (elapsedMs) =>\n new ResourceTimeoutError(\n logicalId,\n resourceType,\n this.stackRegion,\n elapsedMs,\n operationKind,\n timeoutMs\n ),\n }\n );\n } catch (error) {\n renderer.removeTask(logicalId);\n const message = error instanceof Error ? error.message : String(error);\n this.logger.error(`Failed to ${change.changeType.toLowerCase()} ${logicalId}: ${message}`);\n\n throw new ProvisioningError(\n `Failed to ${change.changeType.toLowerCase()} resource ${logicalId}`,\n resourceType,\n logicalId,\n stateResources[logicalId]?.physicalId,\n error instanceof Error ? error : undefined\n );\n } finally {\n // Safety net for early-break paths (UPDATE skip, DeletionPolicy: Retain).\n // removeTask is idempotent, so calling it again after the explicit calls\n // above is a no-op.\n renderer.removeTask(logicalId);\n }\n }\n\n /**\n * Inner body of provisionResource, extracted so the outer wrapper can\n * apply the per-resource deadline (`withResourceDeadline`) without\n * having the timeout / warn timer code dwarf the real provisioning\n * logic. Behaviour is unchanged from the pre-deadline implementation.\n */\n private async provisionResourceBody(\n logicalId: string,\n change: ResourceChange,\n stateResources: Record<string, ResourceState>,\n stackName: string,\n template?: CloudFormationTemplate,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>,\n counts?: { created: number; updated: number; deleted: number; skipped: number },\n progress?: { current: number; total: number }\n ): Promise<void> {\n const resourceType = change.resourceType;\n const provider = this.providerRegistry.getProvider(resourceType);\n const renderer = getLiveRenderer();\n\n switch (change.changeType) {\n case 'CREATE': {\n const desiredProps = change.desiredProperties || {};\n\n // Resolve intrinsic functions in properties\n const context = this.buildResolverContext(\n {\n template: template!,\n resources: stateResources,\n ...(parameterValues && { parameters: parameterValues }),\n ...(conditions && { conditions }),\n },\n stackName\n );\n\n const resolvedProps = (await this.resolver.resolve(desiredProps, context)) as Record<\n string,\n unknown\n >;\n\n // Safety net: if SDK provider doesn't handle all template properties,\n // fall back to CC API for create to ensure no properties are silently dropped\n const { provider: createProvider, properties: createProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n\n const result = await this.withRetry(\n () => createProvider.create(logicalId, resourceType, createProps),\n logicalId,\n undefined,\n undefined,\n provider\n );\n\n // Extract ALL dependencies from template (Ref, Fn::GetAtt, DependsOn)\n // so that deletion order is correct even without implicit type-based deps\n const dependencies = this.extractAllDependencies(template, logicalId);\n const templateAttrs = this.extractTemplateAttributes(template, logicalId);\n\n stateResources[logicalId] = {\n physicalId: result.physicalId,\n resourceType,\n properties: resolvedProps,\n ...(result.attributes && { attributes: result.attributes }),\n ...(dependencies && dependencies.length > 0 && { dependencies }),\n ...templateAttrs,\n };\n\n this.kickOffObservedCapture(\n provider,\n logicalId,\n result.physicalId,\n resourceType,\n resolvedProps\n );\n\n if (counts) counts.created++;\n if (progress) progress.current++;\n const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${createPrefix}✅ ${logicalId} (${resourceType}) created`);\n break;\n }\n\n case 'UPDATE': {\n const currentResource = stateResources[logicalId];\n if (!currentResource) {\n throw new Error(`Cannot update ${logicalId}: resource not found in state`);\n }\n\n const desiredProps = change.desiredProperties || {};\n const currentProps = change.currentProperties || {};\n\n // Resolve intrinsic functions in properties\n const context = this.buildResolverContext(\n {\n template: template!,\n resources: stateResources,\n ...(parameterValues && { parameters: parameterValues }),\n ...(conditions && { conditions }),\n },\n stackName\n );\n\n const resolvedProps = (await this.resolver.resolve(desiredProps, context)) as Record<\n string,\n unknown\n >;\n\n // Re-check diff after resolving intrinsic functions\n // DiffCalculator compares unresolved template vs resolved state, which may produce false positives\n if (JSON.stringify(resolvedProps) === JSON.stringify(currentProps)) {\n // Attribute-only change (schema v5+): `DeletionPolicy` /\n // `UpdateReplacePolicy` may have flipped without any AWS-side\n // property change. There is no per-resource AWS API for those —\n // refresh cdkd state alone and skip the provider call.\n if (change.attributeChanges && change.attributeChanges.length > 0) {\n const attrSummary = change.attributeChanges\n .map((a) => `${a.attribute}: ${a.oldValue ?? '(unset)'} → ${a.newValue ?? '(unset)'}`)\n .join(', ');\n this.logger.info(` ↻ ${logicalId} (${resourceType}) attribute update: ${attrSummary}`);\n stateResources[logicalId] = {\n ...currentResource,\n ...this.extractTemplateAttributes(template, logicalId),\n };\n if (counts) counts.updated++;\n if (progress) progress.current++;\n const attrPrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${attrPrefix}✅ ${logicalId} (${resourceType}) updated (metadata)`);\n break;\n }\n this.logger.debug(\n `Skipping ${logicalId}: no actual changes after intrinsic function resolution`\n );\n if (counts) counts.skipped++;\n break;\n }\n\n // Check if this update requires resource replacement (immutable property changed)\n const needsReplacement = change.propertyChanges?.some((pc) => pc.requiresReplacement);\n\n // Extract ALL dependencies from template (Ref, Fn::GetAtt, DependsOn)\n const dependencies = this.extractAllDependencies(template, logicalId);\n\n if (needsReplacement) {\n // Resource replacement: DELETE old → CREATE new\n const replacedProps = change.propertyChanges\n ?.filter((pc) => pc.requiresReplacement)\n .map((pc) => pc.path)\n .join(', ');\n this.logger.info(\n `Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`\n );\n\n // 1. Create new resource first (CFn order: safe - old resource survives if CREATE fails)\n this.logger.info(` Creating new ${logicalId}...`);\n const { provider: replaceProvider, properties: replaceProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n const createResult = await this.withRetry(\n () => replaceProvider.create(logicalId, resourceType, replaceProps),\n logicalId,\n undefined,\n undefined,\n provider\n );\n\n // 2. Delete old resource (after successful CREATE)\n const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;\n\n if (updateReplacePolicy === 'Retain') {\n this.logger.info(\n ` Retaining old ${logicalId} (${currentResource.physicalId}) - UpdateReplacePolicy: Retain`\n );\n } else {\n this.logger.info(` Deleting old ${logicalId} (${currentResource.physicalId})...`);\n try {\n await provider.delete(\n logicalId,\n currentResource.physicalId,\n resourceType,\n currentResource.properties,\n { expectedRegion: this.stackRegion }\n );\n this.logger.info(` ✓ Old resource deleted`);\n } catch (deleteError) {\n this.logger.warn(\n ` ⚠ Failed to delete old resource ${logicalId} (${currentResource.physicalId}): ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`\n );\n }\n }\n\n stateResources[logicalId] = {\n physicalId: createResult.physicalId,\n resourceType,\n properties: resolvedProps,\n ...(createResult.attributes && { attributes: createResult.attributes }),\n ...(dependencies && dependencies.length > 0 && { dependencies }),\n ...this.extractTemplateAttributes(template, logicalId),\n };\n\n this.kickOffObservedCapture(\n provider,\n logicalId,\n createResult.physicalId,\n resourceType,\n resolvedProps\n );\n\n if (counts) counts.updated++;\n if (progress) progress.current++;\n const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${replacePrefix}✅ ${logicalId} (${resourceType}) replaced`);\n } else {\n // Normal update (in-place)\n this.logger.debug(`Updating ${logicalId} (${resourceType})`);\n\n // Safety net: fall back to CC API if SDK provider doesn't handle all properties\n const { provider: updateProvider, properties: updateProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n\n let result;\n try {\n result = await this.withRetry(\n () =>\n updateProvider.update(\n logicalId,\n currentResource.physicalId,\n resourceType,\n updateProps,\n currentProps\n ),\n logicalId,\n undefined,\n undefined,\n provider\n );\n } catch (updateError) {\n // If UPDATE is not supported (e.g., CC API UnsupportedActionException),\n // fall back to DELETE → CREATE (replacement)\n const msg = updateError instanceof Error ? updateError.message : String(updateError);\n if (\n msg.includes('UnsupportedActionException') ||\n msg.includes('does not support UPDATE')\n ) {\n this.logger.info(\n `UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE → CREATE)`\n );\n try {\n await provider.delete(\n logicalId,\n currentResource.physicalId,\n resourceType,\n currentProps,\n { expectedRegion: this.stackRegion }\n );\n } catch (deleteError) {\n // If old resource doesn't exist (already deleted), proceed with CREATE\n const deleteMsg =\n deleteError instanceof Error ? deleteError.message : String(deleteError);\n if (\n deleteMsg.includes('does not exist') ||\n deleteMsg.includes('not found') ||\n deleteMsg.includes('NotFound')\n ) {\n this.logger.debug(\n `Old resource ${logicalId} already gone, proceeding with CREATE`\n );\n } else {\n throw deleteError;\n }\n }\n const { provider: replProvider, properties: replProps } =\n this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);\n const createResult = await this.withRetry(\n () => replProvider.create(logicalId, resourceType, replProps),\n logicalId,\n undefined,\n undefined,\n provider\n );\n result = {\n physicalId: createResult.physicalId,\n attributes: createResult.attributes,\n wasReplaced: true,\n };\n } else {\n throw updateError;\n }\n }\n\n if (result.wasReplaced) {\n this.logger.info(\n `Resource ${logicalId} was replaced: ${currentResource.physicalId} -> ${result.physicalId}`\n );\n }\n\n stateResources[logicalId] = {\n physicalId: result.physicalId,\n resourceType,\n properties: resolvedProps,\n ...(result.attributes && { attributes: result.attributes }),\n ...(dependencies && dependencies.length > 0 && { dependencies }),\n ...this.extractTemplateAttributes(template, logicalId),\n };\n\n this.kickOffObservedCapture(\n provider,\n logicalId,\n result.physicalId,\n resourceType,\n resolvedProps\n );\n\n if (counts) counts.updated++;\n if (progress) progress.current++;\n const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${updatePrefix}✅ ${logicalId} (${resourceType}) updated`);\n }\n break;\n }\n\n case 'DELETE': {\n const currentResource = stateResources[logicalId];\n if (!currentResource) {\n throw new Error(`Cannot delete ${logicalId}: resource not found in state`);\n }\n\n // Honor `DeletionPolicy: Retain` / `RetainExceptOnCreate`.\n // State is source of truth as of schema v5+ (cdkd records the\n // attribute on every successful create/update). The synth template\n // is consulted as a fallback for pre-v5 state that has no\n // `state.deletionPolicy` recorded yet — once that resource is\n // re-deployed under v5, the state value takes over and stays\n // authoritative even if the user removes the template attribute\n // mid-flight (a destroy mid-PR would otherwise silently downgrade\n // from Retain to Delete on a transient template edit).\n const deletionPolicy =\n currentResource.deletionPolicy ?? template?.Resources?.[logicalId]?.DeletionPolicy;\n if (shouldRetainResource(deletionPolicy)) {\n this.logger.info(\n `Retaining ${logicalId} (${resourceType}) - DeletionPolicy: ${deletionPolicy}`\n );\n delete stateResources[logicalId];\n break;\n }\n\n this.logger.debug(`Deleting ${logicalId} (${resourceType})`);\n try {\n await this.withRetry(\n () =>\n provider.delete(\n logicalId,\n currentResource.physicalId,\n resourceType,\n currentResource.properties,\n { expectedRegion: this.stackRegion }\n ),\n logicalId,\n 3, // fewer retries for DELETE\n 5_000,\n provider\n );\n } catch (deleteError) {\n const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);\n // Treat \"not found\" errors as success (resource already deleted)\n if (\n msg.includes('does not exist') ||\n msg.includes('was not found') ||\n msg.includes('not found') ||\n msg.includes('No policy found') ||\n msg.includes('NoSuchEntity') ||\n msg.includes('NotFoundException') ||\n msg.includes('ResourceNotFoundException')\n ) {\n this.logger.debug(\n `Resource ${logicalId} already deleted (${msg}), removing from state`\n );\n } else {\n throw deleteError;\n }\n }\n\n delete stateResources[logicalId];\n if (counts) counts.deleted++;\n if (progress) progress.current++;\n const deletePrefix = progress ? `[${progress.current}/${progress.total}] ` : ' ';\n renderer.removeTask(logicalId);\n this.logger.info(`${deletePrefix}✅ ${logicalId} (${resourceType}) deleted`);\n break;\n }\n }\n }\n\n /**\n * Create a resource with retry for transient errors\n *\n * Some resources fail immediately after their dependencies are created due to\n * AWS eventual consistency (e.g., Lambda fails if IAM Role hasn't propagated yet).\n * CloudFormation handles this internally; cdkd retries with exponential backoff.\n */\n /**\n * Extract ALL dependencies for a resource from the template.\n *\n * Uses TemplateParser.extractDependencies() to capture Ref, Fn::GetAtt,\n * and DependsOn dependencies. This ensures the state contains complete\n * dependency information for correct deletion ordering (not just DependsOn).\n */\n private extractAllDependencies(\n template: CloudFormationTemplate | undefined,\n logicalId: string\n ): string[] | undefined {\n const resource = template?.Resources?.[logicalId];\n if (!resource) return undefined;\n const parser = new TemplateParser();\n const deps = parser.extractDependencies(resource);\n return deps.size > 0 ? [...deps] : undefined;\n }\n\n /**\n * Read `DeletionPolicy` / `UpdateReplacePolicy` from the synth template\n * so they can be persisted in `ResourceState` (schema v5+). Always returns\n * both keys (`undefined` when the template does not carry the attribute)\n * so that spreading into an existing `ResourceState` reliably overrides a\n * previously-recorded value back to `undefined` — required when the user\n * removes the attribute from their CDK code. `JSON.stringify` then omits\n * the `undefined` keys when state is serialized to S3.\n */\n private extractTemplateAttributes(\n template: CloudFormationTemplate | undefined,\n logicalId: string\n ): {\n deletionPolicy: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n updateReplacePolicy: 'Delete' | 'Retain' | 'Snapshot' | 'RetainExceptOnCreate' | undefined;\n } {\n const resource = template?.Resources?.[logicalId];\n return {\n deletionPolicy: resource?.DeletionPolicy,\n updateReplacePolicy: resource?.UpdateReplacePolicy,\n };\n }\n\n // Type-based implicit deletion ordering rules are defined in\n // src/analyzer/implicit-delete-deps.ts so the deploy DELETE phase and the\n // standalone destroy command apply the same rules.\n\n /**\n * Build a per-resource map of \"must be deleted before me\" dependencies for\n * the DELETE phase, derived from state-recorded dependencies plus implicit\n * type-based ordering rules.\n *\n * For a resource X, the returned set contains every resource Y such that Y\n * must finish deleting before X starts — i.e., Y depends on X (or is otherwise\n * required to vanish first per implicit type rules).\n */\n /**\n * Returns true if the executor still has un-started pending nodes —\n * used to distinguish \"SIGINT cancelled real work\" from \"SIGINT landed\n * after all nodes already completed\" (the latter should not error).\n */\n private hasPending<T>(executor: DagExecutor<T>): boolean {\n for (const node of executor.values()) {\n if (node.state === 'pending') return true;\n }\n return false;\n }\n\n private buildDeletionDependencies(\n deleteIds: Set<string>,\n state: StackState\n ): Map<string, Set<string>> {\n const dependedBy = new Map<string, Set<string>>();\n for (const id of deleteIds) {\n dependedBy.set(id, new Set());\n }\n\n for (const id of deleteIds) {\n const resource = state.resources[id];\n if (!resource?.dependencies) continue;\n for (const dep of resource.dependencies) {\n if (!deleteIds.has(dep)) continue;\n // id depends on dep → dep must be deleted AFTER id (i.e., id is in dep's deletion deps)\n dependedBy.get(dep)!.add(id);\n }\n }\n\n this.addImplicitDeleteDependencies(deleteIds, state, dependedBy);\n\n return dependedBy;\n }\n\n /**\n * Add implicit delete dependency edges based on resource type relationships.\n *\n * Some AWS resources have ordering constraints during deletion that are NOT\n * expressed via Ref/GetAtt in CloudFormation templates. For example, an\n * InternetGateway cannot be deleted until its VPCGatewayAttachment is removed,\n * even though the attachment references the IGW (not the other way around).\n *\n * This method inspects resource types and adds edges so that dependents\n * (e.g., VPCGatewayAttachment) are deleted BEFORE the resources they implicitly\n * depend on (e.g., InternetGateway).\n */\n private addImplicitDeleteDependencies(\n deleteIds: Set<string>,\n state: StackState,\n dependedBy: Map<string, Set<string>>\n ): void {\n // Build a type → logical IDs index for resources being deleted\n const typeToIds = new Map<string, string[]>();\n for (const id of deleteIds) {\n const resource = state.resources[id];\n if (!resource) continue;\n const ids = typeToIds.get(resource.resourceType) ?? [];\n ids.push(id);\n typeToIds.set(resource.resourceType, ids);\n }\n\n for (const id of deleteIds) {\n const resource = state.resources[id];\n if (!resource) continue;\n\n const mustDeleteAfter = IMPLICIT_DELETE_DEPENDENCIES[resource.resourceType];\n if (!mustDeleteAfter) continue;\n\n for (const depType of mustDeleteAfter) {\n const depIds = typeToIds.get(depType);\n if (!depIds) continue;\n\n for (const depId of depIds) {\n // depId (of depType) must be deleted BEFORE id (of resource.resourceType)\n // In the dependedBy map: id is \"depended on\" by depId\n // meaning depId will be picked first (deleted first)\n if (!dependedBy.has(id)) dependedBy.set(id, new Set());\n if (!dependedBy.get(id)!.has(depId)) {\n dependedBy.get(id)!.add(depId);\n this.logger.debug(\n `Implicit delete dependency: ${depId} (${depType}) must be deleted before ${id} (${resource.resourceType})`\n );\n }\n }\n }\n }\n }\n\n /**\n * Select the appropriate provider for create/update, falling back to CC API\n * if the SDK provider doesn't handle all template properties.\n *\n * This safety net prevents properties from being silently dropped when an SDK\n * provider only maps a subset of CloudFormation properties.\n *\n * DELETE always uses the SDK provider (force-delete, cleanup, etc.).\n */\n private selectProviderWithSafetyNet(\n sdkProvider: ResourceProvider,\n resourceType: string,\n resolvedProps: Record<string, unknown>,\n logicalId: string\n ): { provider: ResourceProvider; properties: Record<string, unknown> } {\n const handledSet = sdkProvider.handledProperties?.get(resourceType);\n if (!handledSet) {\n // Provider doesn't declare handledProperties for this type — assume full coverage\n return { provider: sdkProvider, properties: resolvedProps };\n }\n\n const templateProps = Object.keys(resolvedProps);\n const unhandledProps = templateProps.filter((p) => !handledSet.has(p));\n\n if (unhandledProps.length === 0) {\n // All properties are handled by the SDK provider\n return { provider: sdkProvider, properties: resolvedProps };\n }\n\n // There are unhandled properties — try to fall back to CC API\n if (\n CloudControlProvider.isSupportedResourceType(resourceType) &&\n !sdkProvider.disableCcApiFallback\n ) {\n this.logger.info(\n `${logicalId}: SDK provider does not handle [${unhandledProps.join(', ')}] — falling back to CC API for create/update`\n );\n\n // Apply default name generation so CC API uses the same names SDK provider would have.\n // If the provider has custom pre-processing, use that instead.\n const fallbackProps = sdkProvider.preparePropertiesForFallback\n ? sdkProvider.preparePropertiesForFallback(logicalId, resourceType, resolvedProps)\n : applyDefaultNameForFallback(logicalId, resourceType, resolvedProps);\n\n return {\n provider: this.providerRegistry.getCloudControlProvider(),\n properties: fallbackProps,\n };\n }\n\n // CC API fallback not available — fail to prevent silent property loss\n const reason = sdkProvider.disableCcApiFallback\n ? 'CC API fallback is disabled for this provider (known CC API issues)'\n : `CC API does not support ${resourceType}`;\n throw new ProvisioningError(\n `SDK provider for ${resourceType} does not handle properties [${unhandledProps.join(', ')}] ` +\n `and ${reason}. ` +\n `These properties would be silently dropped. ` +\n `Please update the SDK provider to handle all required properties.`,\n resourceType,\n logicalId,\n ''\n );\n }\n\n /**\n * Execute an operation with retry for transient IAM propagation errors.\n *\n * Thin wrapper over `withRetry` from ./retry.js that injects this engine's\n * SIGINT-aware interrupt check and logger. The actual backoff schedule\n * lives there.\n *\n * When the provider opts out via `disableOuterRetry`, the operation is\n * invoked exactly once and the retry loop is skipped entirely. The\n * Custom Resource provider uses this to avoid re-running its `create()`\n * — each invocation derives a fresh pre-signed S3 URL and RequestId,\n * so an outer retry leaves the previous attempt's Lambda response\n * stranded at an S3 key nobody polls.\n */\n private async withRetry<T>(\n operation: () => Promise<T>,\n logicalId: string,\n maxRetries?: number,\n initialDelayMs?: number,\n provider?: ResourceProvider\n ): Promise<T> {\n if (provider?.disableOuterRetry) {\n // Single-shot — provider handles transient errors internally.\n return operation();\n }\n return withRetry(operation, logicalId, {\n ...(maxRetries !== undefined && { maxRetries }),\n ...(initialDelayMs !== undefined && { initialDelayMs }),\n logger: this.logger,\n isInterrupted: () => this.interrupted,\n onInterrupted: () => new InterruptedError(),\n });\n }\n\n /**\n * Resolve stack outputs from template and resource attributes\n *\n * Uses IntrinsicFunctionResolver for full CloudFormation intrinsic function support.\n */\n private async resolveOutputs(\n template: CloudFormationTemplate,\n resources: Record<string, ResourceState>,\n stackName: string,\n parameterValues?: Record<string, unknown>,\n conditions?: Record<string, boolean>\n ): Promise<Record<string, unknown>> {\n if (!template.Outputs) {\n return {};\n }\n\n const outputs: Record<string, unknown> = {};\n const context = this.buildResolverContext(\n {\n template,\n resources,\n ...(parameterValues && { parameters: parameterValues }),\n ...(conditions && { conditions }),\n },\n stackName\n );\n\n for (const [outputKey, output] of Object.entries(template.Outputs)) {\n try {\n const value = await this.resolver.resolve(output.Value, context);\n outputs[outputKey] = value;\n\n // If the output has an Export.Name, also store under that key\n // so Fn::ImportValue can find it by export name\n if (output.Export?.Name) {\n const exportName =\n typeof output.Export.Name === 'string'\n ? output.Export.Name\n : await this.resolver.resolve(output.Export.Name, context);\n if (typeof exportName === 'string') {\n outputs[exportName] = value;\n }\n }\n } catch (error) {\n this.logger.warn(`Failed to resolve output ${outputKey}: ${String(error)}`);\n outputs[outputKey] = undefined;\n }\n }\n\n return outputs;\n }\n\n private buildDisplayOutputs(\n template: CloudFormationTemplate,\n resolvedOutputs: Record<string, unknown>\n ): Record<string, unknown> {\n const display: Record<string, unknown> = {};\n if (!template.Outputs) return display;\n for (const key of Object.keys(template.Outputs)) {\n const v = resolvedOutputs[key];\n if (v !== undefined) display[key] = v;\n }\n return display;\n }\n}\n"],"mappings":"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"}