@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.
- package/README.md +10 -0
- package/dist/{asg-provider-D3bSjRiw.js → asg-provider-BBSPZtne.js} +2 -2
- package/dist/{asg-provider-D3bSjRiw.js.map → asg-provider-BBSPZtne.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{deploy-engine-3lJaFGjA.js → deploy-engine-CSBoL-Az.js} +664 -124
- package/dist/deploy-engine-CSBoL-Az.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-BU6F1UD2.js → program-Def0C7JX.js} +32 -8
- package/dist/{program-BU6F1UD2.js.map → program-Def0C7JX.js.map} +1 -1
- package/dist/{version-B4YqJKXh.js → version-neBHYAq9.js} +2 -2
- package/dist/{version-B4YqJKXh.js.map → version-neBHYAq9.js.map} +1 -1
- package/package.json +1 -1
- package/dist/deploy-engine-3lJaFGjA.js.map +0 -1
|
@@ -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-
|
|
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
|
-
*
|
|
11100
|
-
*
|
|
11101
|
-
*
|
|
11102
|
-
*
|
|
11103
|
-
*
|
|
11104
|
-
*
|
|
11105
|
-
*
|
|
11106
|
-
*
|
|
11107
|
-
*
|
|
11108
|
-
*
|
|
11109
|
-
*
|
|
11110
|
-
*
|
|
11111
|
-
*
|
|
11112
|
-
*
|
|
11113
|
-
*
|
|
11114
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
*
|
|
11486
|
-
*
|
|
11487
|
-
*
|
|
11488
|
-
*
|
|
11489
|
-
*
|
|
11490
|
-
*
|
|
11491
|
-
*
|
|
11492
|
-
*
|
|
11493
|
-
*
|
|
11494
|
-
|
|
11495
|
-
|
|
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))
|
|
11498
|
-
|
|
11499
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
20907
|
-
|
|
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
|
|
20919
|
-
this.logger.
|
|
20920
|
-
|
|
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 {
|
|
32721
|
-
//# sourceMappingURL=deploy-engine-
|
|
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
|