@go-to-k/cdkd 0.285.3 → 0.285.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
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";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-B4YqJKXh.js";
3
+ import { t as getCdkdVersion } from "./version-neBHYAq9.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";
@@ -11096,22 +11096,35 @@ function dynamicReferenceSpans(value) {
11096
11096
  * a MIXED leaf than to a whole token on the identical source, and that
11097
11097
  * inconsistency is what persisted a decrypted secret.
11098
11098
  *
11099
- * ACCEPTED CONSEQUENCE, recorded rather than papered over: on the empty-map
11100
- * paths a genuinely PUBLIC ssm mixed leaf is now OVER-redacted, so the baseline
11101
- * no longer matches AWS and `cdkd drift` reports a phantom on it. That is
11102
- * reachable only through the one documented hole in the premise above — `cdkd
11103
- * import`'s warn path, which can leave a public expression in state. The trade
11104
- * is deliberate and asymmetric: under-redaction persists a decrypted secret,
11105
- * which is a disclosure and is what this lane exists to prevent, while
11106
- * over-redaction is visible, recoverable and discloses nothing. Closing it
11107
- * properly needs a real TYPE classification on these paths, which is issue
11108
- * [#2012](https://github.com/go-to-k/cdkd/issues/2012)'s mechanism; the
11109
- * over-redaction itself is tracked as issue
11110
- * [#2036](https://github.com/go-to-k/cdkd/issues/2036).
11111
- *
11112
- * `tests/integration/secrets-dynamic-ref` is the end-to-end proof on BOTH
11113
- * paths, and it is the only place the empty-map defect surfaced: Phase 1g
11114
- * covers the populated-map deploy and Phase 1f the empty-map command.
11099
+ * NOT CLOSED here. Issue
11100
+ * [#2036](https://github.com/go-to-k/cdkd/issues/2036) tracks the price this
11101
+ * refusal pays: a genuinely PUBLIC ssm mixed leaf is OVER-redacted on the
11102
+ * empty-map paths, so the baseline no longer matches AWS. Giving the empty-map
11103
+ * path POSITIVE evidence (a store of PROVEN-public verdicts, which the
11104
+ * resolver's own `pinSecretVerdict` retraction already computes) was drafted in
11105
+ * PR #2415 and WITHDRAWN there: such a store is keyed on the bare expression
11106
+ * and lives for the whole process, so on a `cdkd deploy --all` spanning regions
11107
+ * a verdict recorded where the parameter is a plain `String` un-redacts a
11108
+ * SecureString of the same name in another region — measured, and the
11109
+ * un-redacting direction, which is worse than the over-redaction it fixes. Any
11110
+ * revival must key the verdict by SCOPE (region + account) at the READ side.
11111
+ *
11112
+ * The residual is therefore the whole population an empty map describes, which
11113
+ * is the state issue #2036 records. Refusing is still the right way to be wrong
11114
+ * here: under-redaction persists a decrypted secret, a disclosure and the thing
11115
+ * this lane exists to prevent, while over-redaction is visible, recoverable and
11116
+ * discloses nothing.
11117
+ *
11118
+ * `tests/integration/secrets-dynamic-ref` is the end-to-end proof, and it is
11119
+ * the only place the empty-map defect surfaced — every unit assertion passed.
11120
+ * Three of its phases pin a DIFFERENT map state for the same two leaves, which
11121
+ * is what makes the split observable rather than asserted: Phase 1 is the
11122
+ * populated-map deploy (the resource is being created), Phase 1g the EMPTY-map
11123
+ * deploy (the resource is UNCHANGED, so it has no per-resource map but the
11124
+ * resolver has still classified the parameter this run), and Phase 1f the
11125
+ * empty-map command, which classifies nothing and therefore still refuses. An
11126
+ * earlier revision of this sentence called Phase 1g the populated-map case,
11127
+ * which is the opposite of what that phase is built to reach.
11115
11128
  */
11116
11129
  function mixedLeafMayCarryPublicReference(source, secrets) {
11117
11130
  if (secrets.size === 0) return false;
@@ -11267,7 +11280,9 @@ function anchorSignature(source) {
11267
11280
  *
11268
11281
  * Two conditions, both required:
11269
11282
  *
11270
- * 1. the key sets (objects) or index counts (arrays) match, and
11283
+ * 1. every SOURCE key is present in the bag (objects) or the index counts match
11284
+ * (arrays) — see the object arm for why containment rather than equality,
11285
+ * and which of the two directions is the fabrication guard, and
11271
11286
  * 2. every position whose SOURCE carries no dynamic reference is deep-equal on
11272
11287
  * both sides -- the *anchors*.
11273
11288
  *
@@ -11307,9 +11322,7 @@ function anchorsCorroboratePairing(bag, source, anchors) {
11307
11322
  if (isDynamicReferenceString(source)) return typeof bag === "string";
11308
11323
  if (isPlainObject$2(source)) {
11309
11324
  if (!isPlainObject$2(bag)) return false;
11310
- const sourceKeys = Object.keys(source);
11311
- if (sourceKeys.length !== Object.keys(bag).length) return false;
11312
- return sourceKeys.every((k) => Object.hasOwn(bag, k) && anchorsCorroboratePairing(bag[k], source[k], anchors));
11325
+ return Object.keys(source).every((k) => Object.hasOwn(bag, k) && anchorsCorroboratePairing(bag[k], source[k], anchors));
11313
11326
  }
11314
11327
  if (Array.isArray(source)) {
11315
11328
  if (!Array.isArray(bag) || bag.length !== source.length) return false;
@@ -11398,6 +11411,394 @@ function unkeyedArrayPairsByAnchors(bag, source) {
11398
11411
  return true;
11399
11412
  }
11400
11413
  /**
11414
+ * Marks a position one of the POSITION passes DECIDED, in the parallel tree
11415
+ * {@link refuseUncertifiedReadbackPositions} builds when asked to `mark`.
11416
+ *
11417
+ * A SENTINEL rather than a value comparison, and that distinction was a
11418
+ * security blocker on PR #2415. {@link preferPositionDecisions} first inferred
11419
+ * "not decided" from `refused === bag`, which cannot tell an UNDECIDED position
11420
+ * from one the pass decided IN FAVOUR of the value already there. Two shapes
11421
+ * hit it, both fabricating a baseline `cdkd drift --revert` then pushes:
11422
+ *
11423
+ * - the resolver's unsupported-service arm leaves an `{{resolve:ssm-secure:`
11424
+ * token LITERAL, so AWS echoes it back and the source leaf EQUALS the bag
11425
+ * leaf. The string arm returns `source` — a decision — and the equality made
11426
+ * it look like no decision at all. (A BARE such token takes the whole-token
11427
+ * arm and one embedded in text takes the mixed-leaf arm; both decide, and
11428
+ * both were misread.)
11429
+ * - the empty-map arm that deliberately KEEPS a leaf returns `bag` by design.
11430
+ *
11431
+ * A symbol cannot be produced by any walk of JSON, so no readback value can
11432
+ * impersonate it.
11433
+ */
11434
+ const POSITION_DECIDED = Symbol("position decided by a position pass");
11435
+ /**
11436
+ * Record one (plaintext -> expression) pair, or strike the plaintext out.
11437
+ *
11438
+ * Below {@link MIN_NEEDLE_LENGTH} nothing is recorded, and this floor DECIDES
11439
+ * rather than mirrors. {@link buildNeedleRegex} applies the same threshold, so
11440
+ * a short needle is dropped from the SUBSTRING arm either way — but the value
11441
+ * scan's other arm is a WHOLE-VALUE lookup (`secrets.get(leaf)`) that matches
11442
+ * at ANY length, so without this line a two-character derived plaintext would
11443
+ * still rewrite every leaf equal to it. That is the false redaction with a
11444
+ * blast radius {@link expressionMaySeedANeedle} exists to bound, arriving by
11445
+ * length instead of by provenance: a public config value of `us` or `dev` is
11446
+ * exactly the kind of short plaintext a readback carries in a dozen unrelated
11447
+ * fields.
11448
+ */
11449
+ function learnNeedle(collector, plaintext, expression) {
11450
+ if (plaintext.length < 4) return;
11451
+ if (collector.poisoned.has(plaintext)) return;
11452
+ if (expressionSecretIsInferred(expression)) collector.inferred.add(plaintext);
11453
+ const already = collector.needles.get(plaintext);
11454
+ if (already === void 0) {
11455
+ collector.needles.set(plaintext, expression);
11456
+ return;
11457
+ }
11458
+ if (already === expression) return;
11459
+ collector.needles.delete(plaintext);
11460
+ collector.poisoned.add(plaintext);
11461
+ }
11462
+ /**
11463
+ * The `{{resolve:<service>:` prefixes whose resolved value IS a secret,
11464
+ * whatever the parameter or secret is called.
11465
+ *
11466
+ * `ssm` is in the list and `ssm-secure` is spelled separately, because
11467
+ * `startsWith('{{resolve:ssm:')` is FALSE for `{{resolve:ssm-secure:` — the
11468
+ * next character is `-`. The two are disjoint tests, not one with a prefix
11469
+ * relationship, which is the trap `mixedLeafMayCarryPublicReference`'s own
11470
+ * comment already records from the other direction.
11471
+ */
11472
+ const SECRET_BEARING_REFERENCE_PREFIXES = [
11473
+ "{{resolve:secretsmanager:",
11474
+ "{{resolve:ssm-secure:",
11475
+ "{{resolve:ssm:"
11476
+ ];
11477
+ /**
11478
+ * The prefixes whose SPELLING settles secret-ness, with no lookup and no
11479
+ * inference — the subset of {@link SECRET_BEARING_REFERENCE_PREFIXES} that
11480
+ * {@link expressionSecretIsInferred} treats as certain.
11481
+ *
11482
+ * An ALLOWLIST rather than "the admission list minus `{{resolve:ssm:`", and the
11483
+ * difference is what happens to the NEXT entry someone adds. Subtracting makes a
11484
+ * new prefix default to CERTAIN, i.e. to the WIDER blast radius, which is the
11485
+ * wrong direction to fail in; listing makes it default to inferred until someone
11486
+ * deliberately promotes it.
11487
+ */
11488
+ const SPELLED_SECRET_REFERENCE_PREFIXES = ["{{resolve:secretsmanager:", "{{resolve:ssm-secure:"];
11489
+ /**
11490
+ * Is this expression's secret-ness INFERRED rather than spelled?
11491
+ *
11492
+ * SPELLING, and ONLY spelling. `secretsmanager:` and `ssm-secure:` say what they
11493
+ * are, in a way that is true in every region and every account. A bare
11494
+ * `{{resolve:ssm:` token is not: it is accepted as secret-bearing on the #1901
11495
+ * premise (a public `String` is persisted RESOLVED, so a token SURVIVING in a
11496
+ * state bag is a `SecureString`), which is sound for the leaf itself and NOT
11497
+ * sound as a licence to rewrite every other leaf that merely CONTAINS the value.
11498
+ *
11499
+ * A RECORDED verdict deliberately does NOT promote one, even though it is a real
11500
+ * `GetParameter` answer. {@link recordedSecretExpressions} is keyed on the bare
11501
+ * expression and lives for the whole process, so on a `cdkd deploy --all` a
11502
+ * verdict pinned where the parameter is a `SecureString` is inherited where it
11503
+ * is a plain `String` — and the `skipDynamicReferences` diff path skips the
11504
+ * lookup on a `true` verdict, so the second region never retracts it. That is
11505
+ * the SAME region blindness this PR withdrew issue #2036's public store for; a
11506
+ * secret-direction verdict is safe to inherit for ADMISSION (it can only
11507
+ * over-redact a leaf) and is not safe for BLAST RADIUS. The cost of ignoring it
11508
+ * here is the substring arm for a verdict-backed, same-region ssm
11509
+ * `SecureString` on the empty-map path — a strict subset of the population the
11510
+ * no-verdict case already concedes, and in the same direction.
11511
+ *
11512
+ * The difference is a `--revert` WRITE. Measured on this module: a bare `ssm`
11513
+ * token whose value is `production` turned `my-production-logs` into
11514
+ * `my-{{resolve:ssm:/app/env}}-logs`, exactly the failure
11515
+ * {@link expressionMaySeedANeedle}'s own doc names — and if that parameter is in
11516
+ * fact public, the baseline now holds a value AWS never reported, which `cdkd
11517
+ * drift --revert` re-resolves and pushes, renaming the live bucket the day the
11518
+ * parameter changes.
11519
+ *
11520
+ * So evidence strength decides BLAST RADIUS, not admission: an inferred needle
11521
+ * still closes issue #2012's two rows, because both are WHOLE-VALUE positions
11522
+ * (an unpaired element and an observed key both hold the plaintext and nothing
11523
+ * else). Only the substring arm is withheld. Issue #2036's withdrawn verdict
11524
+ * store is what would promote these to certain; until it returns, scoped by
11525
+ * region and account, this is the honest bound.
11526
+ */
11527
+ function expressionSecretIsInferred(expression) {
11528
+ return !SPELLED_SECRET_REFERENCE_PREFIXES.some((prefix) => expression.startsWith(prefix));
11529
+ }
11530
+ /**
11531
+ * May this expression's resolved value be used as a REDACTION NEEDLE?
11532
+ *
11533
+ * A stricter question than "may this expression be persisted at this position",
11534
+ * which is what `trustAnyExpression` answers, and the difference is the whole
11535
+ * reason this predicate exists. Persisting a source leaf VERBATIM is bounded to
11536
+ * that one position; promoting the value it replaced to a needle rewrites EVERY
11537
+ * leaf in the record that equals it, so a wrong answer here is a false
11538
+ * redaction with a blast radius rather than a mislabelled leaf.
11539
+ *
11540
+ * Two classes, and each was measured rather than reasoned about:
11541
+ *
11542
+ * - a NON-SECRET SERVICE. `isSingleDynamicReferenceToken` accepts any
11543
+ * `{{resolve:<anything>}}` spelling, and the resolver's unsupported-service
11544
+ * arm WARNS and returns the literal — so AWS holds the token text itself and
11545
+ * the leaf beside it is ordinary data. `cdkd drift`'s own
11546
+ * `--revert does not register a live value for a look-alike spelling` case
11547
+ * pins exactly this for its sibling registration path
11548
+ * (`{{resolve:notaservice:/x}}`), and this predicate is what keeps the two
11549
+ * commands answering it the same way.
11550
+ * - a plain `ssm` reference is ACCEPTED, on the same
11551
+ * #1901 premise the whole-token arm one level up already acts on: a public
11552
+ * `String` / `StringList` parameter is persisted RESOLVED, so a
11553
+ * `{{resolve:ssm:` token SURVIVING in a persisted state bag is a
11554
+ * `SecureString` by construction. Requiring a recorded verdict instead would
11555
+ * make the needle unavailable on `cdkd state refresh-observed`, whose process
11556
+ * resolves nothing and therefore records nothing — i.e. it would fail exactly
11557
+ * where issue #2012 is reported. A PROVEN-public verdict would refine this,
11558
+ * and issue #2036's store was to supply one; PR #2415 withdrew it as a
11559
+ * cross-region disclosure, so a genuinely public parameter's resolved value
11560
+ * CAN still seed a needle here. Bounded by the per-record scope and by
11561
+ * {@link MIN_NEEDLE_LENGTH}, and visible as over-redaction rather than as a
11562
+ * leak.
11563
+ */
11564
+ function expressionMaySeedANeedle(expression) {
11565
+ return isRecordedSecretExpression(expression) || SECRET_BEARING_REFERENCE_PREFIXES.some((prefix) => expression.startsWith(prefix));
11566
+ }
11567
+ /**
11568
+ * Learn from a position whose SOURCE is a WHOLE `{{resolve:...}}` token.
11569
+ *
11570
+ * This is the strongest needle available on a readback path, and it asserts
11571
+ * nothing the walk was not already asserting: the caller is about to persist
11572
+ * `expression` OVER `bag` at this very position, which is the claim that `bag`
11573
+ * is that expression's resolved value. Reading the same claim back out as a
11574
+ * needle is free.
11575
+ *
11576
+ * TWO refusals, both narrow and both necessary:
11577
+ *
11578
+ * - a bag leaf that is ITSELF a complete token is not a plaintext at all. It is
11579
+ * a record that was already redacted (a re-scrub, a second
11580
+ * `refresh-observed`), and pairing it with itself would put a `{{resolve:...}}`
11581
+ * string in the needle set, where the value scan's own token guard would then
11582
+ * have to keep stepping over it.
11583
+ * - a token that does not name a SECRET-BEARING reference at all — see
11584
+ * {@link expressionMaySeedANeedle}.
11585
+ */
11586
+ function learnWholeTokenNeedle(collector, bag, source) {
11587
+ if (isSingleDynamicReferenceToken(bag)) return;
11588
+ if (!expressionMaySeedANeedle(source)) return;
11589
+ learnNeedle(collector, bag, source);
11590
+ }
11591
+ /**
11592
+ * Learn from a MIXED leaf — a reference embedded in surrounding text, which
11593
+ * this module calls the DOMINANT CDK shape (an `Fn::Join` around
11594
+ * `secret.secretValueFromJson(...)`).
11595
+ *
11596
+ * The caller is about to persist `source` over `bag`, i.e. it has already
11597
+ * decided the two are the same leaf one resolution apart. Extracting the
11598
+ * plaintext is then arithmetic rather than inference, PROVIDED the extraction
11599
+ * is unambiguous, which is what the guards below establish:
11600
+ *
11601
+ * - EXACTLY ONE span. With two references the text between them cannot be
11602
+ * split between the two resolved values without guessing. This one is
11603
+ * CONSERVATIVE rather than a correctness guard, and saying so is what stops
11604
+ * the next reader treating it as load-bearing: with two RESOLVED references
11605
+ * the frame check below refuses independently, because the computed SUFFIX
11606
+ * would then contain a whole `{{resolve:...}}` token and a resolved readback
11607
+ * cannot end with one. The shape it genuinely decides is a second reference
11608
+ * that survives LITERALLY in the readback — the resolver's
11609
+ * unsupported-service arm (`ssm-secure:`) produces exactly that — where the
11610
+ * extraction would in fact be right and is declined anyway. Measured: a
11611
+ * both-resolved fixture leaves this line unfenced.
11612
+ * - the source's literal PREFIX and SUFFIX must both be present at the ends of
11613
+ * the bag. That is what proves the leaf really is this source resolved; AWS
11614
+ * normalising any of the surrounding text refuses instead of yielding a
11615
+ * needle sliced at the wrong offsets.
11616
+ * - the two must not overlap, and something must remain between them.
11617
+ *
11618
+ * Anchoring at the ENDS rather than searching is deliberate: a secret whose own
11619
+ * text repeats the suffix (`abc@h` inside `postgres://u:abc@h@h`) still slices
11620
+ * correctly, while an `indexOf` scan would cut it short.
11621
+ */
11622
+ function learnMixedLeafNeedle(collector, bag, source) {
11623
+ const spans = dynamicReferenceSpans(source);
11624
+ if (spans.length !== 1) return;
11625
+ const [span] = spans;
11626
+ const token = source.slice(span.start, span.end);
11627
+ if (!expressionMaySeedANeedle(token)) return;
11628
+ const prefix = source.slice(0, span.start);
11629
+ const suffix = source.slice(span.end);
11630
+ if (bag.length <= prefix.length + suffix.length) return;
11631
+ if (!bag.startsWith(prefix) || !bag.endsWith(suffix)) return;
11632
+ const plaintext = bag.slice(prefix.length, bag.length - suffix.length);
11633
+ if (isSingleDynamicReferenceToken(plaintext)) return;
11634
+ learnNeedle(collector, plaintext, token);
11635
+ }
11636
+ /**
11637
+ * Read the mark tree one level down. It has the SAME shape as `refused` by
11638
+ * construction (one function, one set of inputs), but this stays defensive: a
11639
+ * missing level yields `undefined`, which reads as "not decided" and therefore
11640
+ * lets the scan act. That is the same answer the pre-mark code gave, so a shape
11641
+ * surprise cannot silently start SUPPRESSING redaction — but it fails toward
11642
+ * SCANNING, which is the fabrication direction the mark tree exists to stop.
11643
+ * Both are stated because neither default is free; the shapes are identical by
11644
+ * construction (one function, one set of inputs), so this arm is a backstop
11645
+ * rather than a policy.
11646
+ */
11647
+ function asChild(marks, key) {
11648
+ return isPlainObject$2(marks) && hasPlainPrototype(marks) ? marks[key] : void 0;
11649
+ }
11650
+ /** The array-arm twin of {@link asChild}; same fail-open, same reason. */
11651
+ function asIndex(marks, index) {
11652
+ return Array.isArray(marks) ? marks[index] : void 0;
11653
+ }
11654
+ /**
11655
+ * Merge the DERIVED-needle value scan back over the two POSITION passes, so the
11656
+ * scan can only ever ADD a rewrite and never EDIT one.
11657
+ *
11658
+ * WHY THIS EXISTS AT ALL. Issue #2012's fix has been through both orderings and
11659
+ * each has its own way of turning a redaction fix into a fabricated baseline —
11660
+ * the two are mirror images, which is why the answer is a merge rather than a
11661
+ * third choice of order:
11662
+ *
11663
+ * - SCAN FIRST (the first revision): a needle rewrites a frame LITERAL that
11664
+ * happens to embed a learned plaintext, {@link unkeyedArrayPairsByAnchors} is
11665
+ * then re-run against the SCANNED bag, the anchor no longer deep-equals its
11666
+ * source, the whole array refuses, and a sibling MIXED leaf that position
11667
+ * ALREADY redacted persists in plaintext. Under-redaction.
11668
+ * - SCAN LAST, unrestricted (the second): the scan now runs over leaves whose
11669
+ * content came from the SOURCE. A needle occurring in the literal FRAME of a
11670
+ * source-taken mixed leaf is replaced, so
11671
+ * `postgres://appuser:{{resolve:secretsmanager:...}}@h/db` becomes
11672
+ * `postgres://{{resolve:ssm:/app/db-user}}:{{resolve:...}}@h/db` when
11673
+ * `appuser` is also some whole-token position's resolved value — a reference
11674
+ * the template never had at that offset. `cdkd drift --revert` re-resolves the
11675
+ * baseline before pushing it, so once that parameter's value changes the
11676
+ * revert writes a DIFFERENT user to the live resource. Fabricated baseline,
11677
+ * which is the bar {@link refuseUncertifiedReadbackPositions} refuses to break
11678
+ * at its own bottom.
11679
+ *
11680
+ * THE RULE. A position the passes DECIDED is theirs; a position they left alone
11681
+ * belongs to the scan. Expressed as a walk over their OUTPUT rather than as a
11682
+ * second copy of their pairing logic, because a mirror of `identityKeyFor` /
11683
+ * `unkeyedArrayPairsByAnchors` would drift from the original and a needle
11684
+ * applied at a MIS-paired position is a false redaction everywhere it matches.
11685
+ * The shapes line up position-for-position for free: neither pass adds a key,
11686
+ * an element, or a scalar-over-container, so `refused` is `bag`'s own shape with
11687
+ * some leaves replaced.
11688
+ *
11689
+ * `typeof bag === 'string'` at the leaf: a derived needle can only ever rewrite
11690
+ * a STRING, so for every other leaf the two answers agree and taking `refused`
11691
+ * is free.
11692
+ *
11693
+ * KEEPING A NON-PLAIN LEAF INTACT takes the prototype guard on the object arm,
11694
+ * NOT that leaf rule, and an earlier revision of this comment claimed the
11695
+ * opposite — measured wrong. The scan's own walk rebuilds objects and turns a
11696
+ * `Date` the provider readback carries (`LastModified`) into `{}`; that
11697
+ * flattening predates this module's derived needles on the POPULATED-map path
11698
+ * (issue #2427). The object arm runs FIRST here and `isPlainObject` admits a
11699
+ * `Date`, so without `hasPlainPrototype` this walk did the flattening ITSELF —
11700
+ * newly extending #2427 to the EMPTY-map path, where the unchanged-resource
11701
+ * `drainObservedCaptures` baseline lives and where `cdkd drift --revert` pushes
11702
+ * the result to the live resource. With the guard a non-plain leaf falls
11703
+ * through to `refused`. That is the position passes' own answer — usually the
11704
+ * bag by identity, though NOT universally: their object arm has no prototype
11705
+ * guard of its own, so a non-plain leaf whose source subtree carries a
11706
+ * reference is already flattened one function earlier. Same defect as issue
11707
+ * #2427, one layer up, and out of this lane's scope.
11708
+ *
11709
+ * The net effect is byte-identical to the FIRST ordering on every input where
11710
+ * the un-certification did not fire — which is the whole point: it keeps that
11711
+ * ordering's intent and drops only its defect.
11712
+ */
11713
+ function preferPositionDecisions(scanned, refused, bag, marks, inferred) {
11714
+ if (isPlainObject$2(bag) && hasPlainPrototype(bag) && isPlainObject$2(refused) && isPlainObject$2(scanned)) {
11715
+ const out = Object.create(null);
11716
+ for (const [k, v] of Object.entries(refused)) out[k] = preferPositionDecisions(scanned[k], v, bag[k], asChild(marks, k), inferred);
11717
+ return out;
11718
+ }
11719
+ if (Array.isArray(bag) && Array.isArray(refused) && Array.isArray(scanned) && refused.length === bag.length && scanned.length === bag.length) return refused.map((item, i) => preferPositionDecisions(scanned[i], item, bag[i], asIndex(marks, i), inferred));
11720
+ if (typeof bag !== "string" || marks === POSITION_DECIDED) return refused;
11721
+ return scanned === bag ? inferred.get(bag) ?? scanned : scanned;
11722
+ }
11723
+ /**
11724
+ * DERIVED NEEDLES (issue [#2012](https://github.com/go-to-k/cdkd/issues/2012)):
11725
+ * the secrets map an empty-map readback path can build from its OWN two bags,
11726
+ * with no resolution, no AWS call and no new permission.
11727
+ *
11728
+ * THE PROBLEM THIS ANSWERS. On the readback paths the secrets map is empty by
11729
+ * construction (nothing was resolved), so the value scan has no needles and
11730
+ * POSITION is the only mechanism. Two shapes have no position to argue from and
11731
+ * kept their plaintext: an UNPAIRED array element beside a paired one, and an
11732
+ * observed KEY the source does not carry. Both are places {@link redactByPath}
11733
+ * ALREADY delegates to the value scan — it is the scan that had nothing to say.
11734
+ *
11735
+ * WHAT MAKES A NEEDLE AVAILABLE WITHOUT FETCHING. The same record almost always
11736
+ * carries the same secret at a position the pass DOES certify: the paired
11737
+ * sibling, the key the source does carry. Certifying such a position IS the
11738
+ * assertion that AWS's value there is that expression's resolved form — the
11739
+ * pass acts on it by persisting the expression over it. Reading that assertion
11740
+ * back out gives a plaintext, and a plaintext is exactly what the value scan
11741
+ * was missing. Issue #2012's own direction was to RESOLVE the record's
11742
+ * expressions to get one, which would have made `cdkd state refresh-observed`
11743
+ * and every deploy's observed capture FETCH secrets: a new IAM requirement, a
11744
+ * new failure mode and a new place plaintext lives. None of that is needed —
11745
+ * AWS already handed us the plaintext, in the very bag being redacted.
11746
+ *
11747
+ * `redactByPath`'s own comment argued the opposite direction and was right
11748
+ * about it: seeding the scan from the SOURCE's expressions cannot work, because
11749
+ * a scan needs PLAINTEXT needles. This seeds it from the BAG's values, which is
11750
+ * the half that exists.
11751
+ *
11752
+ * SCOPE, and why each bound is where it is:
11753
+ *
11754
+ * - ONLY the readback-projected rules. Every other caller either has a real map
11755
+ * or has a source of a different generation, where a value learned from one
11756
+ * generation must not rewrite the other.
11757
+ * - ONLY when the map is EMPTY, and this bound is load-bearing for a reason
11758
+ * that is not obvious: {@link crossStackAssociations} and
11759
+ * {@link nestedStackParameterExpressions} are `WeakMap`s keyed by the
11760
+ * RecordedSecretValues INSTANCE, so handing the pipeline a different Map
11761
+ * object would silently lose every association that pass recorded. With an
11762
+ * empty map there are none to lose (an association is only ever recorded for
11763
+ * a plaintext that map holds).
11764
+ * - the pairs are scoped to ONE record, exactly as `perResourceSecrets` is on
11765
+ * the deploy path, so one resource's secret can never rewrite another's
11766
+ * coinciding literal.
11767
+ *
11768
+ * WHERE THE RESULT IS APPLIED, and this is a SEQUENCING claim rather than a
11769
+ * scoping one: the returned map is handed to a plain VALUE pass over the RAW
11770
+ * bag, whose result is then MERGED over the output of both position passes by
11771
+ * {@link preferPositionDecisions}. It reaches neither position pass. Both naive
11772
+ * orderings are wrong and that function's doc has the measurements: scanning
11773
+ * FIRST lets a needle rewrite a frame LITERAL and un-certify an anchor pairing
11774
+ * (under-redaction), scanning LAST over their output lets one rewrite a literal
11775
+ * inside a leaf they took from SOURCE (a fabricated baseline). The merge is what
11776
+ * makes "a derived needle can only ADD rewrites" literally true.
11777
+ *
11778
+ * Returns `undefined` when nothing is learned, so the no-secret path skips the
11779
+ * scan and the merge entirely and stays byte-identical to the position passes'
11780
+ * own output.
11781
+ */
11782
+ function deriveReadbackNeedles(bag, source, secrets, rules) {
11783
+ if (!isReadbackProjectedFromState(rules)) return void 0;
11784
+ if (secrets.size > 0) return void 0;
11785
+ if (!subtreeHasDynamicReference(source)) return void 0;
11786
+ const collector = {
11787
+ needles: /* @__PURE__ */ new Map(),
11788
+ poisoned: /* @__PURE__ */ new Set(),
11789
+ inferred: /* @__PURE__ */ new Set()
11790
+ };
11791
+ refuseUncertifiedReadbackPositions(bag, source, secrets, collector);
11792
+ if (collector.needles.size === 0) return void 0;
11793
+ const certain = /* @__PURE__ */ new Map();
11794
+ const inferred = /* @__PURE__ */ new Map();
11795
+ for (const [plaintext, expression] of collector.needles) (collector.inferred.has(plaintext) ? inferred : certain).set(plaintext, expression);
11796
+ return {
11797
+ certain,
11798
+ inferred
11799
+ };
11800
+ }
11801
+ /**
11401
11802
  * Refuse to persist a readback leaf the path pass could not CERTIFY, at any
11402
11803
  * position the STATE source proves is secret-bearing (issue #1926 review).
11403
11804
  *
@@ -11482,39 +11883,59 @@ function unkeyedArrayPairsByAnchors(bag, source) {
11482
11883
  * --revert` pushes the BASELINE to AWS, so a masked baseline would write the
11483
11884
  * literal `***` onto the live resource (the issue #1498 / #1501 class).
11484
11885
  *
11485
- * KNOWN RESIDUAL, the last row: an observed KEY the source object does not
11486
- * carry has no source leaf to take and no needle to match. It is NOT refused
11487
- * the way an unpaired array ELEMENT is, and the asymmetry is deliberate rather
11488
- * than an oversight an extra array element is a PEER of the secret-bearing
11489
- * ones (another `Environment` entry), so suspicion is warranted and extras are
11490
- * rare, while an extra object KEY is a different FIELD entirely (`Runtime`,
11491
- * `FunctionArn`, `LastModified`) and is the NORM in an AWS readback. Refusing
11492
- * those would empty the drift baseline of every secret-bearing resource.
11493
- * Tracked as issue [#2012](https://github.com/go-to-k/cdkd/issues/2012).
11494
- */
11495
- function refuseUncertifiedReadbackPositions(bag, source, secrets) {
11886
+ * The last two rows were the RESIDUAL and are closed by DERIVED NEEDLES (issue
11887
+ * #2012) see {@link deriveReadbackNeedles}. Neither has a position to argue
11888
+ * from: an unpaired array element and an observed KEY the source does not carry
11889
+ * are both positions with no source leaf to take. What they never lacked was a
11890
+ * VALUE the same plaintext usually sits at a position this pass DOES certify,
11891
+ * and certifying it is already an assertion that the value is that expression's
11892
+ * resolved form. Reading that assertion back out as a needle turns the value
11893
+ * scan on for the rest of the record without resolving anything.
11894
+ *
11895
+ * The extra-KEY asymmetry stays as it was and is worth restating, because the
11896
+ * needle does not replace it: an extra array element is a PEER of the
11897
+ * secret-bearing ones (another `Environment` entry), while an extra object KEY
11898
+ * is a different FIELD entirely (`Runtime`, `FunctionArn`, `LastModified`) and
11899
+ * is the NORM in an AWS readback. Refusing those wholesale would empty the
11900
+ * drift baseline of every secret-bearing resource, which is why they are
11901
+ * value-scanned rather than refused.
11902
+ *
11903
+ * `learn` is the LEARN PASS's collector and is `undefined` on the substituting
11904
+ * pass. It changes NO verdict — every branch below decides exactly what it
11905
+ * decided before — it only records the (plaintext, expression) pairs the
11906
+ * certified positions establish. Deriving them through this function rather
11907
+ * than a second walk is deliberate: the pairing rules (identity keys, anchor
11908
+ * corroboration, the refusals) are subtle enough that a mirror of them would
11909
+ * drift, and a needle learned from a MIS-paired position is a false redaction
11910
+ * everywhere it then matches.
11911
+ */
11912
+ function refuseUncertifiedReadbackPositions(bag, source, secrets, learn, mark) {
11496
11913
  if (isDynamicReferenceString(source) && typeof bag === "string") {
11497
- if (isSingleDynamicReferenceToken(source)) return source;
11498
- if (mixedLeafMayCarryPublicReference(source, secrets)) return bag;
11499
- return source;
11914
+ if (isSingleDynamicReferenceToken(source)) {
11915
+ if (learn) learnWholeTokenNeedle(learn, bag, source);
11916
+ return mark ? POSITION_DECIDED : source;
11917
+ }
11918
+ if (mixedLeafMayCarryPublicReference(source, secrets)) return mark ? POSITION_DECIDED : bag;
11919
+ if (learn) learnMixedLeafNeedle(learn, bag, source);
11920
+ return mark ? POSITION_DECIDED : source;
11500
11921
  }
11501
11922
  if (!subtreeHasDynamicReference(source)) return bag;
11502
11923
  if (isPlainObject$2(bag) && isPlainObject$2(source)) {
11503
11924
  const out = Object.create(null);
11504
- for (const [k, v] of Object.entries(bag)) out[k] = Object.hasOwn(source, k) ? refuseUncertifiedReadbackPositions(v, source[k], secrets) : v;
11925
+ for (const [k, v] of Object.entries(bag)) out[k] = Object.hasOwn(source, k) ? refuseUncertifiedReadbackPositions(v, source[k], secrets, learn, mark) : v;
11505
11926
  return out;
11506
11927
  }
11507
11928
  if (Array.isArray(bag) && Array.isArray(source)) {
11508
11929
  const key = identityKeyFor(bag, source);
11509
11930
  if (key === void 0) {
11510
11931
  if (!unkeyedArrayPairsByAnchors(bag, source)) return bag;
11511
- return bag.map((item, i) => refuseUncertifiedReadbackPositions(item, source[i], secrets));
11932
+ return bag.map((item, i) => refuseUncertifiedReadbackPositions(item, source[i], secrets, learn, mark));
11512
11933
  }
11513
11934
  const sourceByIdentity = /* @__PURE__ */ new Map();
11514
11935
  for (const item of source) sourceByIdentity.set(item[key], item);
11515
11936
  return bag.map((item) => {
11516
11937
  const partner = sourceByIdentity.get(item[key]);
11517
- return partner === void 0 ? item : refuseUncertifiedReadbackPositions(item, partner, secrets);
11938
+ return partner === void 0 ? item : refuseUncertifiedReadbackPositions(item, partner, secrets, learn, mark);
11518
11939
  });
11519
11940
  }
11520
11941
  return bag;
@@ -11532,7 +11953,12 @@ function redactSecretsForState(bag, secrets, source, rules = TEMPLATE_DERIVED_RU
11532
11953
  if (secrets.size === 0 && source === void 0) return bag;
11533
11954
  if (source !== void 0) {
11534
11955
  const positioned = redactByPath(bag, source, secrets, rules, new Set(secrets.values()));
11535
- return isReadbackProjectedFromState(rules) ? refuseUncertifiedReadbackPositions(positioned, source, secrets) : positioned;
11956
+ if (!isReadbackProjectedFromState(rules)) return positioned;
11957
+ const refused = refuseUncertifiedReadbackPositions(positioned, source, secrets);
11958
+ const derived = deriveReadbackNeedles(bag, source, secrets, rules);
11959
+ if (derived === void 0) return refused;
11960
+ const marks = refuseUncertifiedReadbackPositions(positioned, source, secrets, void 0, true);
11961
+ return preferPositionDecisions(redactSecretsForState(bag, derived.certain), refused, bag, marks, derived.inferred);
11536
11962
  }
11537
11963
  const regex = buildNeedleRegex(secrets.keys());
11538
11964
  const wholeValueExpr = (s) => s === "" ? void 0 : secrets.get(s);
@@ -19792,6 +20218,151 @@ function isTerminationProtectionPropagationError(message) {
19792
20218
  return /may not be terminated|disableApiTermination/i.test(message);
19793
20219
  }
19794
20220
 
20221
+ //#endregion
20222
+ //#region src/deployment/delete-outcome.ts
20223
+ /**
20224
+ * Shared helpers over {@link ResourceDeleteResult} — originally the deploy-side
20225
+ * consumption of it (issue
20226
+ * [#1762](https://github.com/go-to-k/cdkd/issues/1762)), the twin of what
20227
+ * `src/cli/commands/destroy-runner.ts` does for `cdkd destroy`, and since issue
20228
+ * [#2301](https://github.com/go-to-k/cdkd/issues/2301) also the PRODUCER-side
20229
+ * `indeterminateGuards` constructor. Write and read live in one file on
20230
+ * purpose: the field's whole job is to survive a hop from a provider to a
20231
+ * recorder, and a sanitizer that does not sit beside its constructor is how
20232
+ * the two drift.
20233
+ *
20234
+ * Issue [#1752](https://github.com/go-to-k/cdkd/issues/1752) gave
20235
+ * `ResourceProvider.delete` an optional return value whose `'skipped'` arm
20236
+ * means **the resource this result names was NOT destroyed and may still be
20237
+ * ALIVE**, and taught the destroy runner to report it. Every OTHER
20238
+ * `provider.delete(...)` call site — the deploy engine's template-DELETE
20239
+ * branch, its four replacement / recreate delete sites, and the five
20240
+ * `rollback-executor.ts` delete arms — discarded the value, so the same skip
20241
+ * printed as `deleted`, counted as `deleted`, and dropped the state record.
20242
+ *
20243
+ * **The module must stay a LEAF — no imports beyond the type, ever.** Same
20244
+ * reason as `src/provisioning/nested-stack-messages.ts`: both the deploy
20245
+ * engine and the rollback executor consume it, and those two already sit on a
20246
+ * dense import ring (engine -> executor -> provider registry -> every
20247
+ * provider). A helper that pulled anything else in would close it.
20248
+ */
20249
+ /**
20250
+ * The `reason` of a `'skipped'` delete outcome, or `undefined` when the
20251
+ * provider reported a delete (`{ outcome: 'deleted' }` or the back-compat
20252
+ * `void` return ~80 providers still use).
20253
+ *
20254
+ * A function rather than an inline `result?.outcome === 'skipped'` test at
20255
+ * ten call sites so the back-compat `void` reading lives in ONE place: the
20256
+ * signature is `Promise<void | ResourceDeleteResult>`, so a caller that awaits
20257
+ * it holds `void | ResourceDeleteResult`, which TypeScript will happily let
20258
+ * you compare against nothing useful.
20259
+ */
20260
+ function deleteSkipReason(result) {
20261
+ if (!result || result.outcome !== "skipped") return void 0;
20262
+ if (typeof result.reason !== "string") return UNSPECIFIED_SKIP_REASON;
20263
+ const trimmed = result.reason.trim();
20264
+ return trimmed === "" ? UNSPECIFIED_SKIP_REASON : trimmed;
20265
+ }
20266
+ /**
20267
+ * Stand-in for a `'skipped'` outcome whose producer supplied no `reason`.
20268
+ *
20269
+ * Deliberately says the cause is unknown rather than inventing one: the line
20270
+ * it renders on is the user's only signal that the resource survived, and a
20271
+ * fabricated cause would send them looking in the wrong place.
20272
+ */
20273
+ const UNSPECIFIED_SKIP_REASON = "no reason reported by the provider";
20274
+ /**
20275
+ * The sentence every deploy-side skip renders, in the log line AND in the
20276
+ * `Error` the sites that must FAIL the resource throw.
20277
+ *
20278
+ * Wording rules, both load-bearing:
20279
+ *
20280
+ * 1. It says the resource was NOT deleted and MAY STILL EXIST. A skip issued
20281
+ * no AWS call at every producer but `NestedStackProvider.delete`, so the
20282
+ * old resource is presumed alive — which is the whole reason a replacement
20283
+ * site cannot proceed to create its replacement beside it.
20284
+ * 2. It must NOT contain any phrase the callers' already-deleted classifiers
20285
+ * substring-match (`does not exist` / `was not found` / `not found` /
20286
+ * `No policy found` / `NoSuchEntity` / `NotFoundException` /
20287
+ * `ResourceNotFoundException`). Reading a skip as "already gone" is exactly
20288
+ * the mis-accounting this change exists to remove, and the deploy engine's
20289
+ * DELETE branch and its update-not-supported fallback each carry such a
20290
+ * classifier. The call sites additionally handle the skip OUTSIDE their
20291
+ * `catch`, so a future `reason` carrying one of those phrases still cannot
20292
+ * reach a classifier — belt and braces, because `reason` is provider text.
20293
+ */
20294
+ function deleteSkippedMessage(logicalId, physicalId, reason, duringClause) {
20295
+ return `cdkd could not address ${logicalId} (${physicalId}) ${duringClause}, so it was NOT deleted and may still exist: ${reason}`;
20296
+ }
20297
+ /**
20298
+ * Attach an {@link IndeterminateGuard} to whatever a delete arm was about to
20299
+ * return (issue [#2301](https://github.com/go-to-k/cdkd/issues/2301)).
20300
+ *
20301
+ * `undefined` in, `undefined` out when there is no guard to carry — so a
20302
+ * provider whose guard reached a verdict keeps returning the back-compat
20303
+ * `void` the ~80 providers that return it use, and nothing about the
20304
+ * existing shape changes on the hot path.
20305
+ *
20306
+ * A `'skipped'` result keeps its outcome and its `reason`: a guard that could
20307
+ * not answer and a delete that could not be addressed are independent facts,
20308
+ * and collapsing either into the other loses one of them.
20309
+ */
20310
+ function withIndeterminateGuard(result, guard) {
20311
+ if (!guard) return result;
20312
+ const indeterminateGuards = [...Array.isArray(result?.indeterminateGuards) ? result.indeterminateGuards : [], guard];
20313
+ if (result && result.outcome === "skipped") return {
20314
+ ...result,
20315
+ indeterminateGuards
20316
+ };
20317
+ return {
20318
+ ...result ?? {},
20319
+ outcome: "deleted",
20320
+ indeterminateGuards
20321
+ };
20322
+ }
20323
+ /**
20324
+ * The guards a delete result reports as INDETERMINATE — those that ran, could
20325
+ * not reach a verdict, and were therefore not enforced while cdkd proceeded
20326
+ * (issue [#2301](https://github.com/go-to-k/cdkd/issues/2301)). Empty for the
20327
+ * overwhelmingly common case, including the back-compat `void` return.
20328
+ *
20329
+ * Defensive in the same shape and for the same reason as
20330
+ * {@link deleteSkipReason}: the value crosses into a DURABLE record
20331
+ * (`deployments/*.jsonl`), providers are the least type-checked layer in the
20332
+ * repo (a hand-built test double, a future arm, a JS provider), and a
20333
+ * malformed entry must degrade to "not reported" rather than crash the delete
20334
+ * path or persist `guard: undefined`. `typeof` rather than `?.trim()` for the
20335
+ * same reason `deleteSkipReason` uses it — a non-string makes `.trim` itself
20336
+ * `undefined`, i.e. a TypeError thrown out of the very path this hardens.
20337
+ *
20338
+ * Entries whose `guard` or `reason` is missing / non-string / blank are
20339
+ * DROPPED rather than defaulted, which is the opposite of `deleteSkipReason`'s
20340
+ * choice and deliberately so: there a default is the user's only signal that a
20341
+ * live resource survived, so inventing `UNSPECIFIED_SKIP_REASON` beats
20342
+ * silence. Here a guard row with no guard id and no cause says only "something
20343
+ * somewhere was not checked", which cannot be acted on — and it would count
20344
+ * toward the destroy summary's tally, turning an unactionable row into a
20345
+ * number the operator has to chase.
20346
+ */
20347
+ function deleteIndeterminateGuards(result) {
20348
+ const raw = result?.indeterminateGuards;
20349
+ if (!Array.isArray(raw)) return [];
20350
+ const out = [];
20351
+ for (const entry of raw) {
20352
+ if (!entry || typeof entry !== "object") continue;
20353
+ const { guard, reason } = entry;
20354
+ if (typeof guard !== "string" || typeof reason !== "string") continue;
20355
+ const trimmedGuard = guard.trim();
20356
+ const trimmedReason = reason.trim();
20357
+ if (trimmedGuard === "" || trimmedReason === "") continue;
20358
+ out.push({
20359
+ guard: trimmedGuard,
20360
+ reason: trimmedReason
20361
+ });
20362
+ }
20363
+ return out;
20364
+ }
20365
+
19795
20366
  //#endregion
19796
20367
  //#region src/provisioning/json-patch-generator.ts
19797
20368
  /**
@@ -20515,6 +21086,19 @@ function requiresCcDeleteIdentityCheck(resourceType) {
20515
21086
  return CC_DELETE_IDENTITY_CHECKED_TYPES.has(resourceType);
20516
21087
  }
20517
21088
  /**
21089
+ * `IndeterminateGuard.guard` for the pre-flight identity confirmation above
21090
+ * (issue [#2301](https://github.com/go-to-k/cdkd/issues/2301)).
21091
+ *
21092
+ * Named for the GUARD, not for the type or the API it happens to probe today:
21093
+ * the value is persisted into `deployments/*.jsonl` and is therefore a user
21094
+ * contract, and the set it fires for is
21095
+ * {@link CC_DELETE_IDENTITY_CHECKED_TYPES} — a set that is expected to grow to
21096
+ * any type whose physical id is globally unique while its resource is
21097
+ * regional. `s3` / `get-bucket-location` in the id would go stale on the first
21098
+ * such addition, and a stale id cannot be corrected without breaking readers.
21099
+ */
21100
+ const CC_DELETE_REGION_IDENTITY_GUARD = "cc-delete-region-identity";
21101
+ /**
20518
21102
  * The region a `GetBucketLocation` answer denotes, canonicalized.
20519
21103
  *
20520
21104
  * Two legacy wire shapes, both still returned, which is why this is a function
@@ -20726,11 +21310,11 @@ var CloudControlProvider = class {
20726
21310
  this.logger.debug(`Deleting resource ${logicalId} (${resourceType}), physical ID: ${physicalId}`);
20727
21311
  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);
20728
21312
  await this.assertRecordedRegionAgainstClient("pre-delete", context?.expectedRegion, resourceType, logicalId, physicalId);
20729
- await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
21313
+ const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
20730
21314
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
20731
21315
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
20732
- const { ASGProvider } = await import("./asg-provider-D3bSjRiw.js").then((n) => n.n);
20733
- return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
21316
+ const { ASGProvider } = await import("./asg-provider-BBSPZtne.js").then((n) => n.n);
21317
+ return withIndeterminateGuard(await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
20734
21318
  }
20735
21319
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
20736
21320
  if (isProtectedEc2Instance) await disableInstanceApiTermination(getAwsClients().ec2, physicalId, this.logger);
@@ -20748,13 +21332,13 @@ var CloudControlProvider = class {
20748
21332
  this.logger.debug(`Delete request submitted for ${logicalId}, token: ${deleteResponse.ProgressEvent.RequestToken}`);
20749
21333
  await this.waitForOperation(deleteResponse.ProgressEvent.RequestToken, logicalId, "DELETE", resourceType);
20750
21334
  this.logger.debug(`Deleted resource ${logicalId}`);
20751
- return;
21335
+ return withIndeterminateGuard(void 0, indeterminateGuard);
20752
21336
  } catch (error) {
20753
21337
  const err = error;
20754
21338
  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")) {
20755
21339
  await this.assertRecordedRegionAgainstClient("not-found", context?.expectedRegion, resourceType, logicalId, physicalId);
20756
21340
  this.logger.debug(`Resource ${logicalId} already deleted (not found), treating as success`);
20757
- return;
21341
+ return withIndeterminateGuard(void 0, indeterminateGuard);
20758
21342
  }
20759
21343
  if (isProtectedEc2Instance && isTerminationProtectionPropagationError(err.message ?? "") && attempt < maxAttempts) {
20760
21344
  this.logger.debug(`Cloud Control delete of ${logicalId} raced the DisableApiTermination flip-off (attempt ${attempt}/${maxAttempts}); re-flipping and retrying`);
@@ -20890,21 +21474,43 @@ var CloudControlProvider = class {
20890
21474
  * indeterminate arm, which PROCEEDS. `src/utils/aws-region-resolver.ts`
20891
21475
  * records the same finding, and the SDK-side guard re-learned it the
20892
21476
  * expensive way.
21477
+ *
21478
+ * RETURN VALUE (issue #2301 item 3). `undefined` means the guard reached a
21479
+ * verdict — it confirmed the region, or the type is unguarded, or the bucket
21480
+ * is absent (a fourth outcome, not an indeterminate one, per the paragraph
21481
+ * above). An {@link IndeterminateGuard} means it could NOT, and the caller
21482
+ * must carry it out through `ResourceDeleteResult` so the destroy runner can
21483
+ * persist a `RESOURCE_GUARD_INDETERMINATE` event. A MISMATCH still throws.
21484
+ *
21485
+ * The two indeterminate arms below produce THREE distinct `reason` texts,
21486
+ * not two, and that is deliberate: the region-resolution arm falls THROUGH
21487
+ * into the no-region warn, so before this change a client whose SDK region
21488
+ * chain REJECTED was reported identically to one that was never asked. The
21489
+ * remedies differ (fix the credential chain / pass `--region` vs. repair the
21490
+ * state record), so the durable record — and the warn beside it — names
21491
+ * which happened.
20893
21492
  */
20894
21493
  async confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context) {
20895
- if (!requiresCcDeleteIdentityCheck(resourceType)) return;
21494
+ if (!requiresCcDeleteIdentityCheck(resourceType)) return void 0;
20896
21495
  const recordedRegion = context?.expectedRegion?.trim();
20897
21496
  let expectedRegion = recordedRegion === void 0 || recordedRegion === "" ? void 0 : recordedRegion;
21497
+ let clientRegionError;
20898
21498
  if (expectedRegion === void 0) try {
20899
21499
  const clientRegion = (await this.cloudControlClient.config.region())?.trim();
20900
21500
  expectedRegion = clientRegion === void 0 || clientRegion === "" ? void 0 : clientRegion;
20901
21501
  } catch (error) {
20902
- this.logger.debug(`Could not resolve the Cloud Control client region while confirming ${physicalId}: ${error instanceof Error ? error.message : String(error)}`);
21502
+ const clientRegionFailure = describeAwsFailure(error);
21503
+ clientRegionError = clientRegionFailure.summary;
21504
+ this.logger.debug(`Could not resolve the Cloud Control client region while confirming ${physicalId}: ${clientRegionFailure.detail}`);
20903
21505
  expectedRegion = void 0;
20904
21506
  }
20905
21507
  if (expectedRegion === void 0) {
20906
- 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.`);
20907
- return;
21508
+ 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}`;
21509
+ this.logger.warn(`Could not confirm that ${resourceType} ${physicalId} (${logicalId}) is the resource this destroy targets: ${reason.replace(/[.\s]+$/, "")}. Proceeding with the delete.`);
21510
+ return {
21511
+ guard: CC_DELETE_REGION_IDENTITY_GUARD,
21512
+ reason
21513
+ };
20908
21514
  }
20909
21515
  const wantRegion = canonicalizeRegion(expectedRegion);
20910
21516
  let actualRegion;
@@ -20915,9 +21521,14 @@ var CloudControlProvider = class {
20915
21521
  this.logger.debug(`Bucket ${physicalId} (${logicalId}) is already absent; leaving the delete to the Cloud Control idempotency path`);
20916
21522
  return;
20917
21523
  }
20918
- const reason = error instanceof Error ? error.message : String(error);
20919
- 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.`);
20920
- return;
21524
+ const failure = describeAwsFailure(error);
21525
+ this.logger.debug(`s3:GetBucketLocation on ${physicalId} (${logicalId}) failed: ${failure.detail}`);
21526
+ const summarySentence = failure.summary.replace(/[.\s]+$/, "");
21527
+ 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.`);
21528
+ return {
21529
+ guard: CC_DELETE_REGION_IDENTITY_GUARD,
21530
+ reason: `s3:GetBucketLocation on ${physicalId} could not be answered: ${failure.summary}`
21531
+ };
20921
21532
  }
20922
21533
  if (actualRegion === wantRegion) {
20923
21534
  this.logger.debug(`Confirmed S3 bucket ${physicalId} (${logicalId}) lives in ${wantRegion} before deleting it`);
@@ -28593,77 +29204,6 @@ async function withResourceDeadline(operation, opts) {
28593
29204
  });
28594
29205
  }
28595
29206
 
28596
- //#endregion
28597
- //#region src/deployment/delete-outcome.ts
28598
- /**
28599
- * Deploy-side consumption of {@link ResourceDeleteResult} (issue
28600
- * [#1762](https://github.com/go-to-k/cdkd/issues/1762)) — the twin of what
28601
- * `src/cli/commands/destroy-runner.ts` does for `cdkd destroy`.
28602
- *
28603
- * Issue [#1752](https://github.com/go-to-k/cdkd/issues/1752) gave
28604
- * `ResourceProvider.delete` an optional return value whose `'skipped'` arm
28605
- * means **the resource this result names was NOT destroyed and may still be
28606
- * ALIVE**, and taught the destroy runner to report it. Every OTHER
28607
- * `provider.delete(...)` call site — the deploy engine's template-DELETE
28608
- * branch, its four replacement / recreate delete sites, and the five
28609
- * `rollback-executor.ts` delete arms — discarded the value, so the same skip
28610
- * printed as `deleted`, counted as `deleted`, and dropped the state record.
28611
- *
28612
- * **The module must stay a LEAF — no imports beyond the type, ever.** Same
28613
- * reason as `src/provisioning/nested-stack-messages.ts`: both the deploy
28614
- * engine and the rollback executor consume it, and those two already sit on a
28615
- * dense import ring (engine -> executor -> provider registry -> every
28616
- * provider). A helper that pulled anything else in would close it.
28617
- */
28618
- /**
28619
- * The `reason` of a `'skipped'` delete outcome, or `undefined` when the
28620
- * provider reported a delete (`{ outcome: 'deleted' }` or the back-compat
28621
- * `void` return ~80 providers still use).
28622
- *
28623
- * A function rather than an inline `result?.outcome === 'skipped'` test at
28624
- * ten call sites so the back-compat `void` reading lives in ONE place: the
28625
- * signature is `Promise<void | ResourceDeleteResult>`, so a caller that awaits
28626
- * it holds `void | ResourceDeleteResult`, which TypeScript will happily let
28627
- * you compare against nothing useful.
28628
- */
28629
- function deleteSkipReason(result) {
28630
- if (!result || result.outcome !== "skipped") return void 0;
28631
- if (typeof result.reason !== "string") return UNSPECIFIED_SKIP_REASON;
28632
- const trimmed = result.reason.trim();
28633
- return trimmed === "" ? UNSPECIFIED_SKIP_REASON : trimmed;
28634
- }
28635
- /**
28636
- * Stand-in for a `'skipped'` outcome whose producer supplied no `reason`.
28637
- *
28638
- * Deliberately says the cause is unknown rather than inventing one: the line
28639
- * it renders on is the user's only signal that the resource survived, and a
28640
- * fabricated cause would send them looking in the wrong place.
28641
- */
28642
- const UNSPECIFIED_SKIP_REASON = "no reason reported by the provider";
28643
- /**
28644
- * The sentence every deploy-side skip renders, in the log line AND in the
28645
- * `Error` the sites that must FAIL the resource throw.
28646
- *
28647
- * Wording rules, both load-bearing:
28648
- *
28649
- * 1. It says the resource was NOT deleted and MAY STILL EXIST. A skip issued
28650
- * no AWS call at every producer but `NestedStackProvider.delete`, so the
28651
- * old resource is presumed alive — which is the whole reason a replacement
28652
- * site cannot proceed to create its replacement beside it.
28653
- * 2. It must NOT contain any phrase the callers' already-deleted classifiers
28654
- * substring-match (`does not exist` / `was not found` / `not found` /
28655
- * `No policy found` / `NoSuchEntity` / `NotFoundException` /
28656
- * `ResourceNotFoundException`). Reading a skip as "already gone" is exactly
28657
- * the mis-accounting this change exists to remove, and the deploy engine's
28658
- * DELETE branch and its update-not-supported fallback each carry such a
28659
- * classifier. The call sites additionally handle the skip OUTSIDE their
28660
- * `catch`, so a future `reason` carrying one of those phrases still cannot
28661
- * reach a classifier — belt and braces, because `reason` is provider text.
28662
- */
28663
- function deleteSkippedMessage(logicalId, physicalId, reason, duringClause) {
28664
- return `cdkd could not address ${logicalId} (${physicalId}) ${duringClause}, so it was NOT deleted and may still exist: ${reason}`;
28665
- }
28666
-
28667
29207
  //#endregion
28668
29208
  //#region src/deployment/update-outcome.ts
28669
29209
  /**
@@ -32717,5 +33257,5 @@ var DeployEngine = class {
32717
33257
  };
32718
33258
 
32719
33259
  //#endregion
32720
- 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 };
32721
- //# sourceMappingURL=deploy-engine-3lJaFGjA.js.map
33260
+ 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 };
33261
+ //# sourceMappingURL=deploy-engine-CSBoL-Az.js.map