@go-to-k/cdkd 0.285.4 → 0.285.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- import { d as generateResourceName, f as generateResourceNameWithFallback, g as withStackName, m as looksLikeCdkdGeneratedName, n as getLogger, o as getLiveRenderer, p as getCurrentStackName, u as applyDefaultNameForFallback } from "./logger-Dz3Le2Pw.js";
1
+ import { _ as withStackName, d as applyDefaultNameForFallback, f as generateResourceName, h as looksLikeCdkdGeneratedName, m as getCurrentStackName, n as getLogger, p as generateResourceNameWithFallback, r as isStdoutReservedForPayload, s as getLiveRenderer } from "./logger-DyTk5GeO.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-Dlq6xZAQ.js";
3
+ import { t as getCdkdVersion } from "./version-De6foYg3.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -5525,8 +5525,13 @@ var FileAssetPublisher = class {
5525
5525
  * Output handling: stdout/stderr are collected in memory unconditionally so
5526
5526
  * `runDockerStreaming` can return them to the caller for error wrapping.
5527
5527
  * When the logger is at debug level (i.e. the user passed `--verbose`),
5528
- * the chunks are ALSO mirrored to `process.stdout` / `process.stderr` so
5529
- * the user sees live build progress.
5528
+ * the chunks are ALSO mirrored live so the user sees build progress —
5529
+ * stderr always to `process.stderr`, and stdout to `process.stdout` EXCEPT
5530
+ * while a command holds a payload reservation
5531
+ * ({@link isStdoutReservedForPayload}), where it joins the logger on stderr
5532
+ * so a child's diagnostics cannot land in the payload
5533
+ * ([#2410](https://github.com/go-to-k/cdkd/issues/2410)). The same
5534
+ * reservation redirects `spawnForeground`'s inherited fd 1 to fd 2.
5530
5535
  */
5531
5536
  /**
5532
5537
  * Return the docker-compatible CLI binary to invoke. Matches CDK CLI:
@@ -5573,7 +5578,8 @@ async function spawnStreaming(cmd, args, options = {}) {
5573
5578
  const stderrChunks = [];
5574
5579
  child.stdout.on("data", (chunk) => {
5575
5580
  stdoutChunks.push(chunk);
5576
- if (streamLive) process.stdout.write(chunk);
5581
+ if (streamLive) if (isStdoutReservedForPayload()) process.stderr.write(chunk);
5582
+ else process.stdout.write(chunk);
5577
5583
  });
5578
5584
  child.stderr.on("data", (chunk) => {
5579
5585
  stderrChunks.push(chunk);
@@ -5617,25 +5623,42 @@ async function spawnStreaming(cmd, args, options = {}) {
5617
5623
  * exit, so the caller can wrap with its own error class.
5618
5624
  *
5619
5625
  * Differs from {@link runDockerStreaming} in two ways:
5620
- * 1. `stdio: 'inherit'` — output is NOT captured, so terminal control codes
5621
- * (color, progress bar overwrites) flow through unchanged. This is the
5622
- * load-bearing reason for the split: `docker pull`'s progress bars only
5623
- * animate properly when stdout is a real TTY connected to the parent.
5626
+ * 1. The child INHERITS descriptors — output is NOT captured, so terminal
5627
+ * control codes (color, progress bar overwrites) flow through
5628
+ * unchanged. That is the load-bearing reason for the split:
5629
+ * `docker pull`'s progress bars only animate when the child writes to a
5630
+ * real TTY rather than a pipe. Under a payload reservation the child's
5631
+ * fd 1 is redirected to the parent's fd 2 rather than piped, precisely
5632
+ * so it keeps a descriptor and not a pipe — though the animation then
5633
+ * depends on STDERR being a terminal, and degrades to plain lines under
5634
+ * `2> file` (issue
5635
+ * [#2410](https://github.com/go-to-k/cdkd/issues/2410)).
5624
5636
  * 2. No `input` / `streamLive` options — inherit-mode has nothing to
5625
5637
  * capture and nothing to mirror.
5626
5638
  *
5627
- * Used by the `--verbose`-mode `docker pull` plumbing in `docker-runner.ts`
5628
- * and `ecr-puller.ts` (visible layer progress). Non-verbose pulls go through
5629
- * {@link runDockerStreaming} so stderr can be folded into the error message.
5639
+ * Used by the `docker pull` plumbing in `docker-runner.ts` and
5640
+ * `ecr-puller.ts`. Those two callers differ, and the difference matters
5641
+ * enough to state: `docker-runner.ts` reaches this only under `--verbose`,
5642
+ * while `ecr-puller.ts` runs it UNCONDITIONALLY, which is what made the
5643
+ * pre-#2410 stdout leak reachable with no flag at all. Non-verbose pulls in
5644
+ * `docker-runner.ts` go through {@link runDockerStreaming} instead, so
5645
+ * stderr can be folded into the error message.
5630
5646
  */
5631
5647
  async function runDockerForeground(args, options = {}) {
5632
5648
  return spawnForeground(getDockerCmd(), args, options);
5633
5649
  }
5634
5650
  /**
5635
- * Foreground (stdio-inherit) spawn — the inherit-mode counterpart to
5651
+ * Foreground (descriptor-inheriting) spawn — the inherit-mode counterpart to
5636
5652
  * {@link spawnStreaming}. Used by {@link runDockerForeground} for docker-CLI
5637
5653
  * subprocesses.
5638
5654
  *
5655
+ * "inherit" is not unqualified, and THIS is the function that qualifies it:
5656
+ * while a command holds a payload reservation
5657
+ * ({@link isStdoutReservedForPayload}) the child's fd 1 is redirected to the
5658
+ * parent's fd 2, so its output cannot land in the payload
5659
+ * ([#2410](https://github.com/go-to-k/cdkd/issues/2410)). stdin and stderr
5660
+ * are inherited either way. See the inline note at the `spawn` call.
5661
+ *
5639
5662
  * The ENOENT branch crafts a docker-specific install hint ("Install Docker
5640
5663
  * (or set CDK_DOCKER ...)"), so non-docker callers reusing this helper
5641
5664
  * would see a misleading error on missing-binary failures. Keep the binary
@@ -5648,7 +5671,11 @@ async function spawnForeground(cmd, args, options = {}) {
5648
5671
  const child = spawn(cmd, args, {
5649
5672
  cwd: options.cwd,
5650
5673
  env,
5651
- stdio: "inherit"
5674
+ stdio: isStdoutReservedForPayload() ? [
5675
+ "inherit",
5676
+ 2,
5677
+ "inherit"
5678
+ ] : "inherit"
5652
5679
  });
5653
5680
  child.once("error", (err) => {
5654
5681
  if (err.code === "ENOENT") {
@@ -20218,6 +20245,151 @@ function isTerminationProtectionPropagationError(message) {
20218
20245
  return /may not be terminated|disableApiTermination/i.test(message);
20219
20246
  }
20220
20247
 
20248
+ //#endregion
20249
+ //#region src/deployment/delete-outcome.ts
20250
+ /**
20251
+ * Shared helpers over {@link ResourceDeleteResult} — originally the deploy-side
20252
+ * consumption of it (issue
20253
+ * [#1762](https://github.com/go-to-k/cdkd/issues/1762)), the twin of what
20254
+ * `src/cli/commands/destroy-runner.ts` does for `cdkd destroy`, and since issue
20255
+ * [#2301](https://github.com/go-to-k/cdkd/issues/2301) also the PRODUCER-side
20256
+ * `indeterminateGuards` constructor. Write and read live in one file on
20257
+ * purpose: the field's whole job is to survive a hop from a provider to a
20258
+ * recorder, and a sanitizer that does not sit beside its constructor is how
20259
+ * the two drift.
20260
+ *
20261
+ * Issue [#1752](https://github.com/go-to-k/cdkd/issues/1752) gave
20262
+ * `ResourceProvider.delete` an optional return value whose `'skipped'` arm
20263
+ * means **the resource this result names was NOT destroyed and may still be
20264
+ * ALIVE**, and taught the destroy runner to report it. Every OTHER
20265
+ * `provider.delete(...)` call site — the deploy engine's template-DELETE
20266
+ * branch, its four replacement / recreate delete sites, and the five
20267
+ * `rollback-executor.ts` delete arms — discarded the value, so the same skip
20268
+ * printed as `deleted`, counted as `deleted`, and dropped the state record.
20269
+ *
20270
+ * **The module must stay a LEAF — no imports beyond the type, ever.** Same
20271
+ * reason as `src/provisioning/nested-stack-messages.ts`: both the deploy
20272
+ * engine and the rollback executor consume it, and those two already sit on a
20273
+ * dense import ring (engine -> executor -> provider registry -> every
20274
+ * provider). A helper that pulled anything else in would close it.
20275
+ */
20276
+ /**
20277
+ * The `reason` of a `'skipped'` delete outcome, or `undefined` when the
20278
+ * provider reported a delete (`{ outcome: 'deleted' }` or the back-compat
20279
+ * `void` return ~80 providers still use).
20280
+ *
20281
+ * A function rather than an inline `result?.outcome === 'skipped'` test at
20282
+ * ten call sites so the back-compat `void` reading lives in ONE place: the
20283
+ * signature is `Promise<void | ResourceDeleteResult>`, so a caller that awaits
20284
+ * it holds `void | ResourceDeleteResult`, which TypeScript will happily let
20285
+ * you compare against nothing useful.
20286
+ */
20287
+ function deleteSkipReason(result) {
20288
+ if (!result || result.outcome !== "skipped") return void 0;
20289
+ if (typeof result.reason !== "string") return UNSPECIFIED_SKIP_REASON;
20290
+ const trimmed = result.reason.trim();
20291
+ return trimmed === "" ? UNSPECIFIED_SKIP_REASON : trimmed;
20292
+ }
20293
+ /**
20294
+ * Stand-in for a `'skipped'` outcome whose producer supplied no `reason`.
20295
+ *
20296
+ * Deliberately says the cause is unknown rather than inventing one: the line
20297
+ * it renders on is the user's only signal that the resource survived, and a
20298
+ * fabricated cause would send them looking in the wrong place.
20299
+ */
20300
+ const UNSPECIFIED_SKIP_REASON = "no reason reported by the provider";
20301
+ /**
20302
+ * The sentence every deploy-side skip renders, in the log line AND in the
20303
+ * `Error` the sites that must FAIL the resource throw.
20304
+ *
20305
+ * Wording rules, both load-bearing:
20306
+ *
20307
+ * 1. It says the resource was NOT deleted and MAY STILL EXIST. A skip issued
20308
+ * no AWS call at every producer but `NestedStackProvider.delete`, so the
20309
+ * old resource is presumed alive — which is the whole reason a replacement
20310
+ * site cannot proceed to create its replacement beside it.
20311
+ * 2. It must NOT contain any phrase the callers' already-deleted classifiers
20312
+ * substring-match (`does not exist` / `was not found` / `not found` /
20313
+ * `No policy found` / `NoSuchEntity` / `NotFoundException` /
20314
+ * `ResourceNotFoundException`). Reading a skip as "already gone" is exactly
20315
+ * the mis-accounting this change exists to remove, and the deploy engine's
20316
+ * DELETE branch and its update-not-supported fallback each carry such a
20317
+ * classifier. The call sites additionally handle the skip OUTSIDE their
20318
+ * `catch`, so a future `reason` carrying one of those phrases still cannot
20319
+ * reach a classifier — belt and braces, because `reason` is provider text.
20320
+ */
20321
+ function deleteSkippedMessage(logicalId, physicalId, reason, duringClause) {
20322
+ return `cdkd could not address ${logicalId} (${physicalId}) ${duringClause}, so it was NOT deleted and may still exist: ${reason}`;
20323
+ }
20324
+ /**
20325
+ * Attach an {@link IndeterminateGuard} to whatever a delete arm was about to
20326
+ * return (issue [#2301](https://github.com/go-to-k/cdkd/issues/2301)).
20327
+ *
20328
+ * `undefined` in, `undefined` out when there is no guard to carry — so a
20329
+ * provider whose guard reached a verdict keeps returning the back-compat
20330
+ * `void` the ~80 providers that return it use, and nothing about the
20331
+ * existing shape changes on the hot path.
20332
+ *
20333
+ * A `'skipped'` result keeps its outcome and its `reason`: a guard that could
20334
+ * not answer and a delete that could not be addressed are independent facts,
20335
+ * and collapsing either into the other loses one of them.
20336
+ */
20337
+ function withIndeterminateGuard(result, guard) {
20338
+ if (!guard) return result;
20339
+ const indeterminateGuards = [...Array.isArray(result?.indeterminateGuards) ? result.indeterminateGuards : [], guard];
20340
+ if (result && result.outcome === "skipped") return {
20341
+ ...result,
20342
+ indeterminateGuards
20343
+ };
20344
+ return {
20345
+ ...result ?? {},
20346
+ outcome: "deleted",
20347
+ indeterminateGuards
20348
+ };
20349
+ }
20350
+ /**
20351
+ * The guards a delete result reports as INDETERMINATE — those that ran, could
20352
+ * not reach a verdict, and were therefore not enforced while cdkd proceeded
20353
+ * (issue [#2301](https://github.com/go-to-k/cdkd/issues/2301)). Empty for the
20354
+ * overwhelmingly common case, including the back-compat `void` return.
20355
+ *
20356
+ * Defensive in the same shape and for the same reason as
20357
+ * {@link deleteSkipReason}: the value crosses into a DURABLE record
20358
+ * (`deployments/*.jsonl`), providers are the least type-checked layer in the
20359
+ * repo (a hand-built test double, a future arm, a JS provider), and a
20360
+ * malformed entry must degrade to "not reported" rather than crash the delete
20361
+ * path or persist `guard: undefined`. `typeof` rather than `?.trim()` for the
20362
+ * same reason `deleteSkipReason` uses it — a non-string makes `.trim` itself
20363
+ * `undefined`, i.e. a TypeError thrown out of the very path this hardens.
20364
+ *
20365
+ * Entries whose `guard` or `reason` is missing / non-string / blank are
20366
+ * DROPPED rather than defaulted, which is the opposite of `deleteSkipReason`'s
20367
+ * choice and deliberately so: there a default is the user's only signal that a
20368
+ * live resource survived, so inventing `UNSPECIFIED_SKIP_REASON` beats
20369
+ * silence. Here a guard row with no guard id and no cause says only "something
20370
+ * somewhere was not checked", which cannot be acted on — and it would count
20371
+ * toward the destroy summary's tally, turning an unactionable row into a
20372
+ * number the operator has to chase.
20373
+ */
20374
+ function deleteIndeterminateGuards(result) {
20375
+ const raw = result?.indeterminateGuards;
20376
+ if (!Array.isArray(raw)) return [];
20377
+ const out = [];
20378
+ for (const entry of raw) {
20379
+ if (!entry || typeof entry !== "object") continue;
20380
+ const { guard, reason } = entry;
20381
+ if (typeof guard !== "string" || typeof reason !== "string") continue;
20382
+ const trimmedGuard = guard.trim();
20383
+ const trimmedReason = reason.trim();
20384
+ if (trimmedGuard === "" || trimmedReason === "") continue;
20385
+ out.push({
20386
+ guard: trimmedGuard,
20387
+ reason: trimmedReason
20388
+ });
20389
+ }
20390
+ return out;
20391
+ }
20392
+
20221
20393
  //#endregion
20222
20394
  //#region src/provisioning/json-patch-generator.ts
20223
20395
  /**
@@ -20941,6 +21113,19 @@ function requiresCcDeleteIdentityCheck(resourceType) {
20941
21113
  return CC_DELETE_IDENTITY_CHECKED_TYPES.has(resourceType);
20942
21114
  }
20943
21115
  /**
21116
+ * `IndeterminateGuard.guard` for the pre-flight identity confirmation above
21117
+ * (issue [#2301](https://github.com/go-to-k/cdkd/issues/2301)).
21118
+ *
21119
+ * Named for the GUARD, not for the type or the API it happens to probe today:
21120
+ * the value is persisted into `deployments/*.jsonl` and is therefore a user
21121
+ * contract, and the set it fires for is
21122
+ * {@link CC_DELETE_IDENTITY_CHECKED_TYPES} — a set that is expected to grow to
21123
+ * any type whose physical id is globally unique while its resource is
21124
+ * regional. `s3` / `get-bucket-location` in the id would go stale on the first
21125
+ * such addition, and a stale id cannot be corrected without breaking readers.
21126
+ */
21127
+ const CC_DELETE_REGION_IDENTITY_GUARD = "cc-delete-region-identity";
21128
+ /**
20944
21129
  * The region a `GetBucketLocation` answer denotes, canonicalized.
20945
21130
  *
20946
21131
  * Two legacy wire shapes, both still returned, which is why this is a function
@@ -21152,11 +21337,11 @@ var CloudControlProvider = class {
21152
21337
  this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
21153
21338
  if (context?.finalSnapshotIdentifier !== void 0) throw new ProvisioningError(`${logicalId} (${resourceType}) requires a final snapshot (DeletionPolicy: Snapshot), but the Cloud Control API delete route has no final-snapshot parameter. Re-run with --skip-final-snapshot after snapshotting manually, or retain the resource.`, resourceType, logicalId, physicalId);
21154
21339
  await this.assertRecordedRegionAgainstClient("pre-delete", context?.expectedRegion, resourceType, logicalId, physicalId);
21155
- await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
21340
+ const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
21156
21341
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
21157
21342
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
21158
- const { ASGProvider } = await import("./asg-provider-sRJzRAe_.js").then((n) => n.n);
21159
- return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
21343
+ const { ASGProvider } = await import("./asg-provider-DsyALMVT.js").then((n) => n.n);
21344
+ return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
21160
21345
  }
21161
21346
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
21162
21347
  if (isProtectedEc2Instance) await disableInstanceApiTermination(getAwsClients().ec2, physicalId, this.logger);
@@ -21174,13 +21359,13 @@ var CloudControlProvider = class {
21174
21359
  this.logger.debug(`Delete request submitted for ${logicalId}, token: ${deleteResponse.ProgressEvent.RequestToken}`);
21175
21360
  await this.waitForOperation(deleteResponse.ProgressEvent.RequestToken, logicalId, "DELETE", resourceType);
21176
21361
  this.logger.debug(`Deleted resource ${logicalId}`);
21177
- return;
21362
+ return withIndeterminateGuard(void 0, indeterminateGuard);
21178
21363
  } catch (error) {
21179
21364
  const err = error;
21180
21365
  if (error instanceof CloudControlOperationFailedError && error.ccOperation === "DELETE" && error.ccErrorCode === "NotFound" || err.name === "ResourceNotFoundException" || err.message?.includes("does not exist") || err.message?.includes("not found") || err.message?.includes("NotFound")) {
21181
21366
  await this.assertRecordedRegionAgainstClient("not-found", context?.expectedRegion, resourceType, logicalId, physicalId);
21182
21367
  this.logger.debug(`Resource ${logicalId} already deleted (not found), treating as success`);
21183
- return;
21368
+ return withIndeterminateGuard(void 0, indeterminateGuard);
21184
21369
  }
21185
21370
  if (isProtectedEc2Instance && isTerminationProtectionPropagationError(err.message ?? "") && attempt < maxAttempts) {
21186
21371
  this.logger.debug(`Cloud Control delete of ${logicalId} raced the DisableApiTermination flip-off (attempt ${attempt}/${maxAttempts}); re-flipping and retrying`);
@@ -21316,21 +21501,43 @@ var CloudControlProvider = class {
21316
21501
  * indeterminate arm, which PROCEEDS. `src/utils/aws-region-resolver.ts`
21317
21502
  * records the same finding, and the SDK-side guard re-learned it the
21318
21503
  * expensive way.
21504
+ *
21505
+ * RETURN VALUE (issue #2301 item 3). `undefined` means the guard reached a
21506
+ * verdict — it confirmed the region, or the type is unguarded, or the bucket
21507
+ * is absent (a fourth outcome, not an indeterminate one, per the paragraph
21508
+ * above). An {@link IndeterminateGuard} means it could NOT, and the caller
21509
+ * must carry it out through `ResourceDeleteResult` so the destroy runner can
21510
+ * persist a `RESOURCE_GUARD_INDETERMINATE` event. A MISMATCH still throws.
21511
+ *
21512
+ * The two indeterminate arms below produce THREE distinct `reason` texts,
21513
+ * not two, and that is deliberate: the region-resolution arm falls THROUGH
21514
+ * into the no-region warn, so before this change a client whose SDK region
21515
+ * chain REJECTED was reported identically to one that was never asked. The
21516
+ * remedies differ (fix the credential chain / pass `--region` vs. repair the
21517
+ * state record), so the durable record — and the warn beside it — names
21518
+ * which happened.
21319
21519
  */
21320
21520
  async confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context) {
21321
- if (!requiresCcDeleteIdentityCheck(resourceType)) return;
21521
+ if (!requiresCcDeleteIdentityCheck(resourceType)) return void 0;
21322
21522
  const recordedRegion = context?.expectedRegion?.trim();
21323
21523
  let expectedRegion = recordedRegion === void 0 || recordedRegion === "" ? void 0 : recordedRegion;
21524
+ let clientRegionError;
21324
21525
  if (expectedRegion === void 0) try {
21325
21526
  const clientRegion = (await this.cloudControlClient.config.region())?.trim();
21326
21527
  expectedRegion = clientRegion === void 0 || clientRegion === "" ? void 0 : clientRegion;
21327
21528
  } catch (error) {
21328
- this.logger.debug(`Could not resolve the Cloud Control client region while confirming ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
21529
+ const clientRegionFailure = describeAwsFailure(error);
21530
+ clientRegionError = clientRegionFailure.summary;
21531
+ this.logger.debug(`Could not resolve the Cloud Control client region while confirming ${physicalId}: ${clientRegionFailure.detail}`);
21329
21532
  expectedRegion = void 0;
21330
21533
  }
21331
21534
  if (expectedRegion === void 0) {
21332
- this.logger.warn(`Could not confirm that ${resourceType} ${physicalId} (${logicalId}) is the resource this destroy targets: neither the stack state nor the AWS client reports a region. Proceeding with the delete.`);
21333
- return;
21535
+ const reason = clientRegionError === void 0 ? `neither the stack state nor the AWS client reports a region` : `the stack state records no region and the AWS client's region could not be resolved: ${clientRegionError}`;
21536
+ this.logger.warn(`Could not confirm that ${resourceType} ${physicalId} (${logicalId}) is the resource this destroy targets: ${reason.replace(/[.\s]+$/, "")}. Proceeding with the delete.`);
21537
+ return {
21538
+ guard: CC_DELETE_REGION_IDENTITY_GUARD,
21539
+ reason
21540
+ };
21334
21541
  }
21335
21542
  const wantRegion = canonicalizeRegion(expectedRegion);
21336
21543
  let actualRegion;
@@ -21341,9 +21548,14 @@ var CloudControlProvider = class {
21341
21548
  this.logger.debug(`Bucket ${physicalId} (${logicalId}) is already absent; leaving the delete to the Cloud Control idempotency path`);
21342
21549
  return;
21343
21550
  }
21344
- const reason = error instanceof Error ? error.message : String(error);
21345
- this.logger.warn(`Could not confirm which region S3 bucket ${physicalId} (${logicalId}) lives in before deleting it: ${reason}. S3 bucket names are globally unique, so cdkd cannot rule out that this name denotes a bucket in another region. Grant s3:GetBucketLocation on the bucket to enable the check. Proceeding with the delete.`);
21346
- return;
21551
+ const failure = describeAwsFailure(error);
21552
+ this.logger.debug(`s3:GetBucketLocation on ${physicalId} (${logicalId}) failed: ${failure.detail}`);
21553
+ const summarySentence = failure.summary.replace(/[.\s]+$/, "");
21554
+ this.logger.warn(`Could not confirm which region S3 bucket ${physicalId} (${logicalId}) lives in before deleting it: ${summarySentence}. S3 bucket names are globally unique, so cdkd cannot rule out that this name denotes a bucket in another region. Grant s3:GetBucketLocation on the bucket to enable the check. Proceeding with the delete.`);
21555
+ return {
21556
+ guard: CC_DELETE_REGION_IDENTITY_GUARD,
21557
+ reason: `s3:GetBucketLocation on ${physicalId} could not be answered: ${failure.summary}`
21558
+ };
21347
21559
  }
21348
21560
  if (actualRegion === wantRegion) {
21349
21561
  this.logger.debug(`Confirmed S3 bucket ${physicalId} (${logicalId}) lives in ${wantRegion} before deleting it`);
@@ -29019,77 +29231,6 @@ async function withResourceDeadline(operation, opts) {
29019
29231
  });
29020
29232
  }
29021
29233
 
29022
- //#endregion
29023
- //#region src/deployment/delete-outcome.ts
29024
- /**
29025
- * Deploy-side consumption of {@link ResourceDeleteResult} (issue
29026
- * [#1762](https://github.com/go-to-k/cdkd/issues/1762)) — the twin of what
29027
- * `src/cli/commands/destroy-runner.ts` does for `cdkd destroy`.
29028
- *
29029
- * Issue [#1752](https://github.com/go-to-k/cdkd/issues/1752) gave
29030
- * `ResourceProvider.delete` an optional return value whose `'skipped'` arm
29031
- * means **the resource this result names was NOT destroyed and may still be
29032
- * ALIVE**, and taught the destroy runner to report it. Every OTHER
29033
- * `provider.delete(...)` call site — the deploy engine's template-DELETE
29034
- * branch, its four replacement / recreate delete sites, and the five
29035
- * `rollback-executor.ts` delete arms — discarded the value, so the same skip
29036
- * printed as `deleted`, counted as `deleted`, and dropped the state record.
29037
- *
29038
- * **The module must stay a LEAF — no imports beyond the type, ever.** Same
29039
- * reason as `src/provisioning/nested-stack-messages.ts`: both the deploy
29040
- * engine and the rollback executor consume it, and those two already sit on a
29041
- * dense import ring (engine -> executor -> provider registry -> every
29042
- * provider). A helper that pulled anything else in would close it.
29043
- */
29044
- /**
29045
- * The `reason` of a `'skipped'` delete outcome, or `undefined` when the
29046
- * provider reported a delete (`{ outcome: 'deleted' }` or the back-compat
29047
- * `void` return ~80 providers still use).
29048
- *
29049
- * A function rather than an inline `result?.outcome === 'skipped'` test at
29050
- * ten call sites so the back-compat `void` reading lives in ONE place: the
29051
- * signature is `Promise<void | ResourceDeleteResult>`, so a caller that awaits
29052
- * it holds `void | ResourceDeleteResult`, which TypeScript will happily let
29053
- * you compare against nothing useful.
29054
- */
29055
- function deleteSkipReason(result) {
29056
- if (!result || result.outcome !== "skipped") return void 0;
29057
- if (typeof result.reason !== "string") return UNSPECIFIED_SKIP_REASON;
29058
- const trimmed = result.reason.trim();
29059
- return trimmed === "" ? UNSPECIFIED_SKIP_REASON : trimmed;
29060
- }
29061
- /**
29062
- * Stand-in for a `'skipped'` outcome whose producer supplied no `reason`.
29063
- *
29064
- * Deliberately says the cause is unknown rather than inventing one: the line
29065
- * it renders on is the user's only signal that the resource survived, and a
29066
- * fabricated cause would send them looking in the wrong place.
29067
- */
29068
- const UNSPECIFIED_SKIP_REASON = "no reason reported by the provider";
29069
- /**
29070
- * The sentence every deploy-side skip renders, in the log line AND in the
29071
- * `Error` the sites that must FAIL the resource throw.
29072
- *
29073
- * Wording rules, both load-bearing:
29074
- *
29075
- * 1. It says the resource was NOT deleted and MAY STILL EXIST. A skip issued
29076
- * no AWS call at every producer but `NestedStackProvider.delete`, so the
29077
- * old resource is presumed alive — which is the whole reason a replacement
29078
- * site cannot proceed to create its replacement beside it.
29079
- * 2. It must NOT contain any phrase the callers' already-deleted classifiers
29080
- * substring-match (`does not exist` / `was not found` / `not found` /
29081
- * `No policy found` / `NoSuchEntity` / `NotFoundException` /
29082
- * `ResourceNotFoundException`). Reading a skip as "already gone" is exactly
29083
- * the mis-accounting this change exists to remove, and the deploy engine's
29084
- * DELETE branch and its update-not-supported fallback each carry such a
29085
- * classifier. The call sites additionally handle the skip OUTSIDE their
29086
- * `catch`, so a future `reason` carrying one of those phrases still cannot
29087
- * reach a classifier — belt and braces, because `reason` is provider text.
29088
- */
29089
- function deleteSkippedMessage(logicalId, physicalId, reason, duringClause) {
29090
- return `cdkd could not address ${logicalId} (${physicalId}) ${duringClause}, so it was NOT deleted and may still exist: ${reason}`;
29091
- }
29092
-
29093
29234
  //#endregion
29094
29235
  //#region src/deployment/update-outcome.ts
29095
29236
  /**
@@ -33143,5 +33284,5 @@ var DeployEngine = class {
33143
33284
  };
33144
33285
 
33145
33286
  //#endregion
33146
- export { maskerOrIdentity as $, CFN_TEMPLATE_BODY_LIMIT as $n, maskSecretsInText as $t, renderStatefulReason as A, describeAwsFailure as An, PartialFailureError as Ar, requireConfigString as At, exportAliasCollisionScrubWarning as B, Synthesizer as Bn, normalizeAwsError as Br, INTRINSIC_KEYS as Bt, isFinalSnapshotError as C, getBootstrapMarkerKey as Cn, DynamicReferenceRegionAmbiguousError as Cr, coerceCfnBoolean as Ct, extractDeploymentEventError as D, validateAssetBucketName as Dn, LockError as Dr, replayWarn as Dt, makeCanonicalizePropertiesFn as E, readBootstrapMarkerBody as En, LocalStartServiceError as Er, readConfigString as Et, green as F, partitionSensitiveEnv as Fn, StackTerminationProtectionError as Fr, s3BucketDualStackDomainName as Ft, IAMRoleProvider as G, resolveAutoAssetStorage as Gn, isTransientServerError as Gr, STATE_SOURCED_CROSS_GENERATION_RULES as Gt, secretBearingStateKeyWarning as H, getDefaultStateBucketName as Hn, isMarkedNonRetryable as Hr, withRetry as Ht, red as I, runDockerForeground as In, StateError as Ir, s3BucketRegionalDomainName as It, ProviderRegistry as J, resolveStateBucketWithDefault as Jn, retryClassificationText as Jr, createSecretMasker as Jt, collectInlinePolicyNamesManagedBySiblings as K, resolveCaptureObservedState as Kn, markNonRetryable as Kr, STATE_SOURCED_READBACK_RULES as Kt, yellow as L, runDockerStreaming as Ln, SynthesisError as Lr, s3BucketWebsiteUrl as Lt, bold as M, dockerSpawnEnvWithSensitive as Mn, ResourceTimeoutError as Mr, producerRegionsFromState as Mt, cyan as N, formatDockerLoginError as Nn, ResourceUpdateNotSupportedError as Nr, s3BucketArn as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, validateContainerRepoName as On, MissingCdkCliError as Or, requireConfigArray as Ot, gray as P, getDockerCmd as Pn, StackHasActiveImportsError as Pr, s3BucketDomainName as Pt, maskDeep as Q, warnDeprecatedNoPrefixCliFlag as Qn, maskSecretsInError as Qt, collectDeclaredOutputNames as R, AssetManifestLoader as Rn, formatError as Rr, applyRoleArnIfSet as Rt, createPreDeleteFinalSnapshot as S, ensureAssetStorage as Sn, DeployCancelledError as Sr, assertRegionMatch as St, unsupportedFinalSnapshotError as T, parseBootstrapMarker as Tn, LocalMigrateError as Tr, configStringRefusal as Tt, stateKeySecretExposure as U, getLegacyStateBucketName as Un, isRetryableTransientError as Ur, DagBuilder as Ut, isExportAliasCollision as V, synthesisStatusMessage as Vn, withErrorHandling as Vr, describeTypeWithThrottleRetry as Vt, getCurrentResourceSecrets as W, resolveApp as Wn, isThrottlingError as Wr, TemplateParser as Wt, findSilentDropProperties as X, resolveUseCdkBootstrapAssets as Xn, errorCauseChain as Xt, findActionableSilentDrops as Y, resolveStateBucketWithDefaultAndSource as Yn, __exportAll as Yr, dynamicReferenceTokens as Yt, createMaskedRetryLogger as Z, stateBucketExistenceConfirmed as Zn, isSingleDynamicReferenceToken as Zt, computeImplicitDeleteEdges as _, escapeRegExp$1 as _n, AssetError as _r, parameterTypeMayLoseSecretIdentity as _t, DeploymentEventsStore as a, rebuildClientForBucketRegion as an, expectedOwnerParam as ar, isInterruptedWaitError as at, buildFinalSnapshotIdentifier as b, BOOTSTRAP_MARKER_PREFIX as bn, CrossAccountSecretRefusalError as br, normalizeAwsTagsToCfn as bt, replayFailedOperations as c, importableOutputs as cn, derivePartitionAndUrlSuffix as cr, slowCcOperationTimeoutMs as ct, updatePartialReason as d, stringifyValue as dn, clearBucketRegionCache as dr, IntrinsicFunctionResolver as dt, redactSecretsForState as en, CFN_TEMPLATE_URL_LIMIT as er, CUSTOM_RESOURCE_RESPONSE_PREFIX as et, UNSPECIFIED_SKIP_REASON as f, WorkGraph as fn, resolveBucketRegion as fr, carriesDynamicReference as ft, IMPLICIT_DELETE_DEPENDENCIES as g, rewriteTemplateAssetReferences as gn, setAwsClients as gr, isUnboundTemplateParameter as gt, maskingRetryLogger as h, loadPublishableAssetManifest as hn, resetAwsClients as hr, getAccountInfo as ht, DeploymentEventsReader as i, S3StateBackend as in, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as ir, interruptWatchListenerCount as it, formatResourceLine as j, buildDockerImage as jn, ProvisioningError as jr, classifyReplaySecretRegion as jt, isStatefulRecreateTargetSync as k, buildDenyExternalAccessPolicy as kn, NestedStackChildDirectDestroyError as kr, requireConfigObject as kt, replayRollback as l, shouldRetainResource as ln, AssemblyReader as lr, disableInstanceApiTermination as lt, withResourceDeadline as m, createAssetRedirectResolver as mn, getAwsClients as mr, coerceParameterTypedValue as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, LockManager as nn, findLargeInlineResources as nr, beginCommandInterruptScope as nt, planFailedOps as o, exportNamesCarriedFrom as on, PARTITION_TABLE as or, startInterruptWatch as ot, deleteSkipReason as p, buildAssetRedirectMap as pn, AwsClients as pr, cfnRefValueFromPhysicalId as pt, clearOnUpdateRemoval as q, resolveSkipPrefix as qn, markRedactedCause as qr, TEMPLATE_SOURCED_RULES as qt, DeployEngine as r, displaySafe as rn, uploadCfnTemplate as rr, endCommandInterruptScope as rt, planRollback as s, importableOutputKeys as sn, canonicalizeRegion as sr, CloudControlProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, scrubResourceRecord as tn, MIGRATE_TMP_PREFIX as tr, DEFAULT_STATE_PREFIX as tt, updatePartialMessage as u, AssetPublisher as un, processStackMessages as ur, isTerminationProtectionPropagationError as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, stripControlChars as vn, CdkdError as vr, refStateLookupFromResource as vt, refusesFinalSnapshot as w, isCrossRegionRedirect as wn, LocalInvokeBuildError as wr, configBooleanRefusal as wt, ccRoutedFinalSnapshotError as x, assertAssetBucketRegion as xn, DependencyError as xr, resolveExplicitPhysicalId as xt, PRE_DELETE_SNAPSHOT_TYPES as y, AssetModeResolver as yn, ConfigError as yr, WAFv2WebACLProvider as yt, collectPublishedOutputNames as z, getDockerImageBySourceHash as zn, isCdkdError as zr, DiffCalculator as zt };
33147
- //# sourceMappingURL=deploy-engine-3EmPzxSN.js.map
33287
+ export { DEFAULT_STATE_PREFIX as $, warnDeprecatedNoPrefixCliFlag as $n, maskSecretsInError as $t, bold as A, buildDenyExternalAccessPolicy as An, NestedStackChildDirectDestroyError as Ar, requireConfigObject as At, secretBearingStateKeyWarning as B, getDockerImageBySourceHash as Bn, isCdkdError as Br, DiffCalculator as Bt, unsupportedFinalSnapshotError as C, ensureAssetStorage as Cn, DeployCancelledError as Cr, assertRegionMatch as Ct, isStatefulRecreateTargetSync as D, readBootstrapMarkerBody as Dn, LocalStartServiceError as Dr, readConfigString as Dt, MULTI_REGION_RECREATE_BLOCKED_TYPES as E, parseBootstrapMarker as En, LocalMigrateError as Er, configStringRefusal as Et, yellow as F, getDockerCmd as Fn, StackHasActiveImportsError as Fr, s3BucketDomainName as Ft, clearOnUpdateRemoval as G, resolveApp as Gn, isThrottlingError as Gr, TemplateParser as Gt, getCurrentResourceSecrets as H, synthesisStatusMessage as Hn, withErrorHandling as Hr, describeTypeWithThrottleRetry as Ht, collectDeclaredOutputNames as I, partitionSensitiveEnv as In, StackTerminationProtectionError as Ir, s3BucketDualStackDomainName as It, findSilentDropProperties as J, resolveSkipPrefix as Jn, markRedactedCause as Jr, TEMPLATE_SOURCED_RULES as Jt, ProviderRegistry as K, resolveAutoAssetStorage as Kn, isTransientServerError as Kr, STATE_SOURCED_CROSS_GENERATION_RULES as Kt, collectPublishedOutputNames as L, runDockerForeground as Ln, StateError as Lr, s3BucketRegionalDomainName as Lt, gray as M, buildDockerImage as Mn, ProvisioningError as Mr, classifyReplaySecretRegion as Mt, green as N, dockerSpawnEnvWithSensitive as Nn, ResourceTimeoutError as Nr, producerRegionsFromState as Nt, renderStatefulReason as O, validateAssetBucketName as On, LockError as Or, replayWarn as Ot, red as P, formatDockerLoginError as Pn, ResourceUpdateNotSupportedError as Pr, s3BucketArn as Pt, CUSTOM_RESOURCE_RESPONSE_PREFIX as Q, stateBucketExistenceConfirmed as Qn, isSingleDynamicReferenceToken as Qt, exportAliasCollisionScrubWarning as R, runDockerStreaming as Rn, SynthesisError as Rr, s3BucketWebsiteUrl as Rt, refusesFinalSnapshot as S, assertAssetBucketRegion as Sn, DependencyError as Sr, resolveExplicitPhysicalId as St, extractDeploymentEventError as T, isCrossRegionRedirect as Tn, LocalInvokeBuildError as Tr, configBooleanRefusal as Tt, IAMRoleProvider as U, getDefaultStateBucketName as Un, isMarkedNonRetryable as Ur, withRetry as Ut, stateKeySecretExposure as V, Synthesizer as Vn, normalizeAwsError as Vr, INTRINSIC_KEYS as Vt, collectInlinePolicyNamesManagedBySiblings as W, getLegacyStateBucketName as Wn, isRetryableTransientError as Wr, DagBuilder as Wt, maskDeep as X, resolveStateBucketWithDefaultAndSource as Xn, __exportAll as Xr, dynamicReferenceTokens as Xt, createMaskedRetryLogger as Y, resolveStateBucketWithDefault as Yn, retryClassificationText as Yr, createSecretMasker as Yt, maskerOrIdentity as Z, resolveUseCdkBootstrapAssets as Zn, errorCauseChain as Zt, PRE_DELETE_SNAPSHOT_TYPES as _, rewriteTemplateAssetReferences as _n, setAwsClients as _r, isUnboundTemplateParameter as _t, DeploymentEventsStore as a, S3StateBackend as an, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as ar, CloudControlProvider as at, createPreDeleteFinalSnapshot as b, AssetModeResolver as bn, ConfigError as br, WAFv2WebACLProvider as bt, replayFailedOperations as c, importableOutputKeys as cn, canonicalizeRegion as cr, deleteIndeterminateGuards as ct, updatePartialReason as d, AssetPublisher as dn, processStackMessages as dr, isTerminationProtectionPropagationError as dt, maskSecretsInText as en, CFN_TEMPLATE_BODY_LIMIT as er, beginCommandInterruptScope as et, withResourceDeadline as f, stringifyValue as fn, clearBucketRegionCache as fr, IntrinsicFunctionResolver as ft, ATOMIC_FINAL_SNAPSHOT_TYPES as g, loadPublishableAssetManifest as gn, resetAwsClients as gr, getAccountInfo as gt, computeImplicitDeleteEdges as h, createAssetRedirectResolver as hn, getAwsClients as hr, coerceParameterTypedValue as ht, DeploymentEventsReader as i, displaySafe as in, uploadCfnTemplate as ir, startInterruptWatch as it, cyan as j, describeAwsFailure as jn, PartialFailureError as jr, requireConfigString as jt, formatResourceLine as k, validateContainerRepoName as kn, MissingCdkCliError as kr, requireConfigArray as kt, replayRollback as l, importableOutputs as ln, derivePartitionAndUrlSuffix as lr, deleteSkipReason as lt, IMPLICIT_DELETE_DEPENDENCIES as m, buildAssetRedirectMap as mn, AwsClients as mr, cfnRefValueFromPhysicalId as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, scrubResourceRecord as nn, MIGRATE_TMP_PREFIX as nr, interruptWatchListenerCount as nt, planFailedOps as o, rebuildClientForBucketRegion as on, expectedOwnerParam as or, slowCcOperationTimeoutMs as ot, maskingRetryLogger as p, WorkGraph as pn, resolveBucketRegion as pr, carriesDynamicReference as pt, findActionableSilentDrops as q, resolveCaptureObservedState as qn, markNonRetryable as qr, STATE_SOURCED_READBACK_RULES as qt, DeployEngine as r, LockManager as rn, findLargeInlineResources as rr, isInterruptedWaitError as rt, planRollback as s, exportNamesCarriedFrom as sn, PARTITION_TABLE as sr, UNSPECIFIED_SKIP_REASON as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, redactSecretsForState as tn, CFN_TEMPLATE_URL_LIMIT as tr, endCommandInterruptScope as tt, updatePartialMessage as u, shouldRetainResource as un, AssemblyReader as ur, disableInstanceApiTermination as ut, buildFinalSnapshotIdentifier as v, escapeRegExp$1 as vn, AssetError as vr, parameterTypeMayLoseSecretIdentity as vt, makeCanonicalizePropertiesFn as w, getBootstrapMarkerKey as wn, DynamicReferenceRegionAmbiguousError as wr, coerceCfnBoolean as wt, isFinalSnapshotError as x, BOOTSTRAP_MARKER_PREFIX as xn, CrossAccountSecretRefusalError as xr, normalizeAwsTagsToCfn as xt, ccRoutedFinalSnapshotError as y, stripControlChars as yn, CdkdError as yr, refStateLookupFromResource as yt, isExportAliasCollision as z, AssetManifestLoader as zn, formatError as zr, applyRoleArnIfSet as zt };
33288
+ //# sourceMappingURL=deploy-engine-BgpCWFvY.js.map