@go-to-k/cdkd 0.284.77 → 0.284.78

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,5 +1,5 @@
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-C9-E73FI.js";
2
- import { t as getCdkdVersion } from "./version-rsHq2qI3.js";
2
+ import { t as getCdkdVersion } from "./version-GEIr41xq.js";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -9158,9 +9158,13 @@ function splitGetAttStringForm(getAtt) {
9158
9158
  * (`(producer X / Y)`), so it is neither derivable from the source leaf nor
9159
9159
  * stable.
9160
9160
  *
9161
- * THREE arms answer: `Fn::ImportValue`, `Fn::GetStackOutput`, and the
9162
- * `Fn::GetAtt` on a nested-stack OUTPUT that issue #2055's read site
9163
- * re-resolves. Every other leaf refuses.
9161
+ * FIVE arms answer, and the enumeration is kept current because a stale one
9162
+ * reads as exhaustive: `Fn::ImportValue`, `Fn::GetStackOutput`, the `Fn::GetAtt`
9163
+ * on a nested-stack OUTPUT that issue #2055's read site re-resolves, the
9164
+ * single-placeholder `Fn::Sub` that normalizes ONTO that `Fn::GetAtt` key
9165
+ * (issue #2270 round 3 -- this list said THREE until issue #2291 noticed it had
9166
+ * been four since then), and the `Ref` to a nested-stack CHILD's own PARAMETER
9167
+ * (issue #2291). Every other leaf refuses.
9164
9168
  *
9165
9169
  * `Region` and `RoleArn` are OPTIONAL slots, and an ABSENT one keys as empty
9166
9170
  * while a PRESENT-but-non-literal one refuses. Absent has to be its own key
@@ -9263,6 +9267,11 @@ function crossStackSourceKey(source) {
9263
9267
  split.attributeName
9264
9268
  ].join(CROSS_STACK_KEY_SEPARATOR);
9265
9269
  }
9270
+ if (key === "Ref") {
9271
+ const parameterName = literalStringOrUndefined(source[key]);
9272
+ if (parameterName === void 0) return void 0;
9273
+ return ["Ref", parameterName].join(CROSS_STACK_KEY_SEPARATOR);
9274
+ }
9266
9275
  }
9267
9276
  /**
9268
9277
  * Remember, FOR THE PASS THAT OWNS `secrets`, that the cross-stack source leaf
@@ -9282,6 +9291,34 @@ function recordCrossStackExpression(secrets, key, expression, plaintext) {
9282
9291
  associations = /* @__PURE__ */ new Map();
9283
9292
  crossStackAssociations.set(secrets, associations);
9284
9293
  }
9294
+ storeAssociation(associations, key, expression, plaintext);
9295
+ }
9296
+ /**
9297
+ * The store's WRITE rule, shared by {@link recordCrossStackExpression} and
9298
+ * {@link recordNestedStackParameterExpressions} so the two tables cannot drift
9299
+ * apart on what a second sighting means.
9300
+ *
9301
+ * A key recorded against a DIFFERENT (expression, plaintext) pair is POISONED
9302
+ * to {@link CONFLICTING_CROSS_STACK} rather than overwritten; an already
9303
+ * poisoned key stays poisoned, because a third sighting cannot un-contradict
9304
+ * the first two. Every reader refuses a poisoned key, which degrades to the
9305
+ * value scan — today's behaviour — rather than guessing between the two.
9306
+ *
9307
+ * WHAT IS AND IS NOT FENCED, stated because a reader would otherwise assume the
9308
+ * whole thing is (review of issue #2291). The POISON-vs-OVERWRITE decision IS
9309
+ * fenced: a second sighting under a THIRD expression makes the two outcomes
9310
+ * differ (poison refuses and falls back; overwriting would certify the third),
9311
+ * and a case asserts it. Nothing fences the branch from
9312
+ * {@link recordNestedStackParameterExpressions}'s side in PRODUCTION, because
9313
+ * that writer runs once per resource bag and `Object.entries` cannot yield one
9314
+ * name twice — the poison is reachable only through this module's API. It is
9315
+ * kept as an INVARIANT, not claimed as covered behaviour.
9316
+ *
9317
+ * The `isSingleDynamicReferenceToken` shape invariant is deliberately NOT here:
9318
+ * it belongs to each writer, which is where the parameters are named and where
9319
+ * a swap could be introduced.
9320
+ */
9321
+ function storeAssociation(associations, key, expression, plaintext) {
9285
9322
  const seen = associations.get(key);
9286
9323
  if (seen === void 0) {
9287
9324
  associations.set(key, {
@@ -9294,6 +9331,218 @@ function recordCrossStackExpression(secrets, key, expression, plaintext) {
9294
9331
  if (seen.expression !== expression || seen.plaintext !== plaintext) associations.set(key, CONFLICTING_CROSS_STACK);
9295
9332
  }
9296
9333
  /**
9334
+ * The `AWS::CloudFormation::Stack` type string, named once because the recorder
9335
+ * below gates on it and the tests assert against the same population.
9336
+ *
9337
+ * DUPLICATED, deliberately: `intrinsic-function-resolver.ts` declares the same
9338
+ * literal under the same name (for the `Outputs.<Name>` re-resolution of issue
9339
+ * #2055). This module is a LEAF by design -- see the file header, it imports
9340
+ * nothing, because both the resolver and the deploy engine consume it -- so
9341
+ * importing that spelling would close a cycle, and exporting this one for the
9342
+ * resolver to import would make the leaf a source of values rather than of
9343
+ * pure functions. The two cannot drift into DISAGREEMENT in any way that
9344
+ * matters: an AWS resource type string is fixed by AWS, and a typo in either
9345
+ * copy makes that copy's gate simply never fire (no-op), never fire wrongly.
9346
+ */
9347
+ const NESTED_STACK_RESOURCE_TYPE$1 = "AWS::CloudFormation::Stack";
9348
+ /**
9349
+ * What a PARENT stack's resolution proved about each `Parameters` entry of an
9350
+ * `AWS::CloudFormation::Stack` row it is about to provision, keyed by the
9351
+ * child's PARAMETER NAME (issue
9352
+ * [#2291](https://github.com/go-to-k/cdkd/issues/2291)).
9353
+ *
9354
+ * WHY A THIRD STORE, when {@link crossStackAssociations} already holds
9355
+ * per-leaf associations. This one is OUTBOUND: it is written against the
9356
+ * PARENT's bag and describes leaves of the CHILD's template, so it has to be
9357
+ * transported across the engine boundary before any reader can use it. The
9358
+ * child's per-resource bag then receives those entries as ORDINARY
9359
+ * {@link crossStackAssociations} rows (see
9360
+ * {@link inheritNestedStackParameterAssociations}), which is what lets the
9361
+ * existing three-condition reader answer for them with no new arm.
9362
+ *
9363
+ * KEEPING THE TWO TABLES SEPARATE IS LOAD-BEARING, not tidiness. A child engine
9364
+ * that itself owns a grandchild `AWS::CloudFormation::Stack` row records the
9365
+ * GRANDCHILD's parameter names against the CHILD's bag — the same bag that
9366
+ * already carries the child's own INBOUND `Ref` associations. Writing both into
9367
+ * one table means a grandchild parameter sharing a NAME with a child parameter
9368
+ * poisons the child's entry (two expressions under one key), so a leaf that was
9369
+ * being certified correctly falls back to the value scan the moment a
9370
+ * same-named parameter appears one level down. Two tables make the collision
9371
+ * impossible rather than unlikely.
9372
+ *
9373
+ * A `WeakMap` keyed by the parent pass's own bag, for the reason
9374
+ * {@link crossStackAssociations} gives: the entries hold PLAINTEXT, and they
9375
+ * must not outlive the pass that fetched them.
9376
+ */
9377
+ const nestedStackParameterExpressions = /* @__PURE__ */ new WeakMap();
9378
+ /**
9379
+ * Record, for the pass that owns `secrets`, which `{{resolve:...}}` expression
9380
+ * each `Parameters` entry of a nested-stack row was resolved FROM (issue
9381
+ * #2291). No-op for every other resource type.
9382
+ *
9383
+ * THE EXPRESSIONS COME FROM THE POSITION PASS, not from `secrets`, and that is
9384
+ * the whole reason this works at all. `RecordedSecretValues` is keyed by
9385
+ * PLAINTEXT, so two parameters resolving to one value have already collapsed to
9386
+ * a single entry there by the time this runs — asking the map which expression
9387
+ * a given parameter came from returns the SURVIVOR for both. What has not
9388
+ * collapsed is the parent's own template: `Properties.Parameters.<Name>` still
9389
+ * holds each entry's own source leaf. So this walks the parent's resolved
9390
+ * parameter bag against that source with {@link redactSecretsForState}, which
9391
+ * is exactly the machinery the parent's persist path already uses and which
9392
+ * certifies PER LEAF (measured on this issue: the parent's own two properties
9393
+ * come out correct while the child's collapse onto one). Deriving the answer
9394
+ * from the positioner rather than restating its rules is what keeps this from
9395
+ * drifting away from the persist path.
9396
+ *
9397
+ * THE `rules` PARAMETER IS THE CALLER'S GENERATION CLAIM, not a knob. The
9398
+ * DEPLOY path passes {@link TEMPLATE_DERIVED_RULES} (the default): its source is
9399
+ * the parent's TEMPLATE, which can carry a PUBLIC `ssm:` reference that must
9400
+ * stay resolved (issue #1901) and which cannot certify the generation of
9401
+ * anything. The rollback REPLAY passes {@link STATE_DERIVED_RULES}, because its
9402
+ * source is the JOURNAL record — a persisted bag holding no public expressions,
9403
+ * and the same generation the bag was resolved from one statement earlier. That
9404
+ * is the identical pairing `redactRollbackRecord` already makes for the record
9405
+ * it positions, so the two replay walks now agree about what their source is.
9406
+ *
9407
+ * "No public expressions" carries the carve-out {@link PathSourceRules} states
9408
+ * and this note must not restate without it: `cdkd import` warns and persists
9409
+ * the RAW template intrinsic, so a public `ssm:` token CAN sit in a record. The
9410
+ * replay then CERTIFIES one the deploy default would refuse, at a cost bounded
9411
+ * to the issue #1901 class — a spurious UPDATE over a value state should hold
9412
+ * resolved, never a disclosure, since an expression is what gets persisted
9413
+ * either way. See the replay call sites for why gating on
9414
+ * {@link isKnownSecretExpression} is the wrong way to close it.
9415
+ *
9416
+ * FOUR REFUSALS, each degrading to today's behaviour (the child leaf falls to
9417
+ * the plaintext-keyed value scan):
9418
+ *
9419
+ * 1. REFUSAL — the resolved parameter value is not a WHOLE recorded plaintext.
9420
+ * A parameter the parent built with an `Fn::Sub` merely EMBEDS the secret,
9421
+ * so there is no single expression the child's leaf could be persisted as.
9422
+ * It also bounds what this store HOLDS: an entry carries a plaintext, and
9423
+ * remembering one this pass never resolved has no purpose.
9424
+ * 2. REFUSAL — the expression is not a single complete `{{resolve:...}}` token.
9425
+ * The same test {@link recordCrossStackExpression} applies at its own
9426
+ * boundary, spelled here because this writer populates a DIFFERENT table.
9427
+ * This is what rejects an embedded case that survived refusal 1 (a value
9428
+ * scan produced `postgres://u:{{resolve:...}}@host`, which is not a token).
9429
+ * 3. REFUSAL — the expression may never EQUAL the plaintext it is recorded
9430
+ * against. The reader returns the expression to be PERSISTED, so an entry
9431
+ * whose two halves coincide hands a SECRET back as though it were a
9432
+ * reference.
9433
+ *
9434
+ * THIS IS REACHABLE, and an earlier revision of this note called it an
9435
+ * unreachable invariant and told the next reader not to try to fence it.
9436
+ * That was wrong twice over: wrong on the fact, and wrong to assert it,
9437
+ * because {@link plaintextIndexOf}'s own note records the rule that
9438
+ * "asserting something cannot be fenced suppresses the attempt, so it needs
9439
+ * the same evidence a fence does" -- and no such evidence existed. The
9440
+ * reaching shape is the issue
9441
+ * [#1917](https://github.com/go-to-k/cdkd/issues/1917) family: a
9442
+ * SELF-REFERENTIAL secret, whose stored VALUE is byte-identical to its own
9443
+ * `{{resolve:...}}` text, so the pass records `SELF -> SELF`. The route is
9444
+ * NOT the value scan the old note named. It is {@link redactByPath}'s
9445
+ * `!sourceIsSameGeneration && isSingleDynamicReferenceToken(bag)` arm, which
9446
+ * returns `secrets.get(bag) ?? bag` -- and for a self-referential secret
9447
+ * that IS the bag. Under {@link STATE_DERIVED_RULES} the same input arrives
9448
+ * by the other door (`sourceIsSameGeneration` is true, so the arm takes
9449
+ * `return source`, which is the same string again), so both callers reach it.
9450
+ * Fenced by the self-referential case in
9451
+ * `secret-redaction-nested-parameter-source.test.ts`.
9452
+ */
9453
+ function recordNestedStackParameterExpressions(secrets, resourceType, resolvedProperties, sourceProperties, rules = TEMPLATE_DERIVED_RULES) {
9454
+ if (resourceType !== NESTED_STACK_RESOURCE_TYPE$1) return;
9455
+ if (secrets.size === 0) return;
9456
+ if (!isPlainObject$2(resolvedProperties) || !isPlainObject$2(sourceProperties)) return;
9457
+ if (!Object.hasOwn(resolvedProperties, "Parameters")) return;
9458
+ if (!Object.hasOwn(sourceProperties, "Parameters")) return;
9459
+ const resolvedParameters = resolvedProperties["Parameters"];
9460
+ const sourceParameters = sourceProperties["Parameters"];
9461
+ if (!isPlainObject$2(resolvedParameters) || !isPlainObject$2(sourceParameters)) return;
9462
+ const positioned = redactSecretsForState(resolvedParameters, secrets, sourceParameters, rules);
9463
+ let table = nestedStackParameterExpressions.get(secrets);
9464
+ for (const [name, resolvedValue] of Object.entries(resolvedParameters)) {
9465
+ if (typeof resolvedValue !== "string" || !secrets.has(resolvedValue)) continue;
9466
+ const expression = positioned[name];
9467
+ if (typeof expression !== "string") continue;
9468
+ if (!isSingleDynamicReferenceToken(expression)) continue;
9469
+ const sourceLeaf = sourceParameters[name];
9470
+ if (typeof sourceLeaf === "string" && expression !== sourceLeaf) continue;
9471
+ if (expression === resolvedValue) continue;
9472
+ if (table === void 0) {
9473
+ table = /* @__PURE__ */ new Map();
9474
+ nestedStackParameterExpressions.set(secrets, table);
9475
+ }
9476
+ storeAssociation(table, name, expression, resolvedValue);
9477
+ }
9478
+ }
9479
+ /**
9480
+ * Copy a parent pass's per-PARAMETER associations onto a nested-stack CHILD
9481
+ * resource's own bag, as ordinary {@link crossStackAssociations} rows keyed by
9482
+ * `{Ref: <ParamName>}` (issue #2291).
9483
+ *
9484
+ * Called by the child {@link DeployEngine} once per resolver context, so each
9485
+ * child resource's fresh bag carries them. Pre-seeding every resource this way
9486
+ * does NOT reintroduce issue #2087's over-redaction: an association can only
9487
+ * change an answer through {@link positionByCrossStackSource}'s condition 1,
9488
+ * which requires the bag leaf to be a plaintext THIS resource's bag holds — and
9489
+ * since #2087 that is true only of resources whose own resolution consumed the
9490
+ * parameter. The scoping still comes from the plaintext bag; this table only
9491
+ * decides WHICH expression such a leaf takes.
9492
+ *
9493
+ * Nothing is copied when the parent recorded nothing, which is every non-nested
9494
+ * caller and every nested one whose parameters carry no secret.
9495
+ */
9496
+ function inheritNestedStackParameterAssociations(childSecrets, parentSecrets) {
9497
+ const table = nestedStackParameterExpressions.get(parentSecrets);
9498
+ if (table === void 0 || table.size === 0) return;
9499
+ let associations = crossStackAssociations.get(childSecrets);
9500
+ for (const [name, association] of table) {
9501
+ const key = crossStackSourceKey({ Ref: name });
9502
+ if (key === void 0) continue;
9503
+ if (associations === void 0) {
9504
+ associations = /* @__PURE__ */ new Map();
9505
+ crossStackAssociations.set(childSecrets, associations);
9506
+ }
9507
+ if (typeof association === "symbol") {
9508
+ associations.set(key, association);
9509
+ continue;
9510
+ }
9511
+ storeAssociation(associations, key, association.expression, association.plaintext);
9512
+ }
9513
+ }
9514
+ /**
9515
+ * The expression a nested-stack child's PARAMETER was resolved from, or
9516
+ * `undefined` when this pass cannot certify one (issue #2291).
9517
+ *
9518
+ * The DIFF-side twin of {@link positionByCrossStackSource}, and it must exist
9519
+ * or the fix trades one bug for another. The child's persisted state now holds
9520
+ * each parameter-fed leaf's OWN expression, so the desired side of the next
9521
+ * diff has to hold it too; the engine's `redactParametersForDiff` rewrites the
9522
+ * parameter bag through the plaintext-keyed map alone, which hands BOTH members
9523
+ * of a coinciding pair the survivor's expression. The two sides would then
9524
+ * never match for the losing parameter: a perpetual UPDATE on every deploy of
9525
+ * such a child, which is issue #2087's user-visible symptom arriving through a
9526
+ * different door.
9527
+ *
9528
+ * `parentSecrets` is the INHERITED bag — the parent's own per-resource map, the
9529
+ * object this table is keyed by — not the child resource's bag.
9530
+ *
9531
+ * The same THREE conditions {@link positionByCrossStackSource} applies, for the
9532
+ * same reasons; see that function's own notes for why condition 3 is not
9533
+ * subsumed by condition 2.
9534
+ */
9535
+ function inheritedParameterExpression(parentSecrets, parameterName, resolvedValue) {
9536
+ if (typeof resolvedValue !== "string" || resolvedValue === "") return void 0;
9537
+ if (!parentSecrets.has(resolvedValue)) return void 0;
9538
+ const association = nestedStackParameterExpressions.get(parentSecrets)?.get(parameterName);
9539
+ if (association === void 0 || typeof association === "symbol") return void 0;
9540
+ if (association.plaintext !== resolvedValue) return void 0;
9541
+ const recordedPlaintext = plaintextIndexOf(parentSecrets).get(association.expression);
9542
+ if (recordedPlaintext !== void 0 && recordedPlaintext !== resolvedValue) return void 0;
9543
+ return association.expression;
9544
+ }
9545
+ /**
9297
9546
  * A resolved secret value shorter than this is NOT used as a redaction needle:
9298
9547
  * a 1-2 character plaintext (e.g. a secret whose JSON key holds `"0"`) would
9299
9548
  * match incidental characters everywhere and mangle unrelated state. Such a
@@ -16602,15 +16851,79 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
16602
16851
  * `Fn::Select` / `Fn::FindInMap` all re-enter `resolveValue` and reach the
16603
16852
  * parameter through this same `Ref` branch.
16604
16853
  *
16854
+ * WHICH EXPRESSION the pair is recorded against is the issue
16855
+ * [#2291](https://github.com/go-to-k/cdkd/issues/2291) round-2 fix, and
16856
+ * skipping it made the persist and diff halves DISAGREE.
16857
+ *
16858
+ * `inherited` is keyed by PLAINTEXT, so two parent parameters resolving to
16859
+ * ONE value collapse to a single entry there and this method used to copy
16860
+ * whichever expression SURVIVED. That is invisible for a leaf spelled exactly
16861
+ * `{Ref: P}` — the persist path positions such a leaf through the parent's
16862
+ * per-parameter association and never consults this bag's value — but every
16863
+ * EMBEDDING shape (`Fn::Sub`, `Fn::Join`, and `{'Fn::Sub': '${P}'}`, which
16864
+ * `crossStackSourceKey` refuses because its `Fn::Sub` arm requires a dotted
16865
+ * attribute) falls to the plaintext-keyed VALUE SCAN, which reads exactly this
16866
+ * bag. Meanwhile `DeployEngine.redactParametersForDiff` answers PER PARAMETER.
16867
+ * So `Fn::Sub "postgres://u:${LoserParam}@host"` — the dominant CDK
16868
+ * connection-string shape — persisted the SURVIVOR's expression while the
16869
+ * desired side computed the LOSER's, and the two never matched again: a
16870
+ * perpetual UPDATE, or a perpetual REPLACEMENT on a create-only property.
16871
+ * That is issue [#2087](https://github.com/go-to-k/cdkd/issues/2087)'s symptom
16872
+ * arriving through a different door, prevented for the bare-`Ref` leaf and
16873
+ * created for the embedded one.
16874
+ *
16875
+ * Recording THIS parameter's own expression makes the value scan agree with
16876
+ * the diff side, so both halves move together.
16877
+ *
16878
+ * THE RESIDUAL, and the earlier version of this note UNDERSTATED IT. It said
16879
+ * the leftover case was "no worse than the behaviour before this fix". That
16880
+ * is true only of the shape it was measured on. MEASURED 2026-08-27 against
16881
+ * `main` (f56c2cf9) and against this branch, one child resource, parameters
16882
+ * `A` and `B` resolving to one plaintext:
16883
+ *
16884
+ * | shape | main | here |
16885
+ * | ---------------------------------------- | ------- | ------- |
16886
+ * | `{Ref: A}` and `{Ref: B}` | agree | agree |
16887
+ * | `Fn::Sub '${A}'` only | agree | agree |
16888
+ * | `Fn::Sub 'x${A}'` **and** `{Ref: B}` | agree | DISAGREE|
16889
+ *
16890
+ * The third row is a NEW disagreement this PR introduces, not a pre-existing
16891
+ * one it fails to fix: `main` had both halves take the collapsed survivor, so
16892
+ * they matched (on the WRONG expression, which is issue #2291, but they
16893
+ * matched). Here the DIFF side is per-parameter while an EMBEDDED leaf can
16894
+ * only be redacted by the plaintext-keyed value scan, and this bag holds ONE
16895
+ * entry — whichever `Ref` resolved LAST. So the embedded leaf takes `B`'s
16896
+ * expression while the desired side computes `A`'s, and the resource reports
16897
+ * an UPDATE on every deploy (a REPLACEMENT, on a create-only property).
16898
+ *
16899
+ * It is ORDER-DEPENDENT, which is why it is narrow: reversing the two
16900
+ * properties makes the embedded parameter the last one resolved and the two
16901
+ * halves agree again (measured). It also needs BOTH leaves in ONE resource —
16902
+ * `perResourceSecrets` is keyed by logical id, so two resources get two bags
16903
+ * and each is right. A resource that consumes and embeds BOTH parameters is
16904
+ * unfixable here for the same reason and is genuinely inherent.
16905
+ *
16906
+ * CLOSING IT NEEDS A PLACEHOLDER-SPAN POSITION ARM — aligning an `Fn::Sub` /
16907
+ * `Fn::Join` source against the resolved string to locate each placeholder's
16908
+ * span and rewrite it from the association. That is a new positioning
16909
+ * CONCEPT rather than an arm beside the existing ones: the persist path would
16910
+ * have to reproduce the resolver's substitution semantics from a module that
16911
+ * holds neither a resolver nor a parameter bag, and any divergence between
16912
+ * the two reproductions is this same perpetual-UPDATE bug. Deferred to issue
16913
+ * [#2320](https://github.com/go-to-k/cdkd/issues/2320) with the measurement.
16914
+ * It shares only the word "span" with issue #2102, which registers live
16915
+ * values for `{{resolve:...}}` TOKEN spans on the drift paths.
16916
+ *
16605
16917
  * Substituting is deliberately NOT done here — the resolved value is what
16606
16918
  * reaches AWS, and an `Fn::Equals` over a parameter must compare the real
16607
16919
  * value or the condition flips.
16608
16920
  */
16609
- recordInheritedParameterSecrets(value, context) {
16921
+ recordInheritedParameterSecrets(parameterName, value, context) {
16610
16922
  const inherited = context.inheritedSecrets;
16611
16923
  const recorded = context.recordedSecretValues;
16612
16924
  if (!inherited || inherited.size === 0 || !recorded) return;
16613
- for (const [plaintext, expression] of inheritedSecretsCarriedBy(value, inherited)) recorded.set(plaintext, expression);
16925
+ const own = inheritedParameterExpression(inherited, parameterName, value);
16926
+ for (const [plaintext, expression] of inheritedSecretsCarriedBy(value, inherited)) recorded.set(plaintext, own !== void 0 && plaintext === value ? own : expression);
16614
16927
  }
16615
16928
  /**
16616
16929
  * Refuse a child parameter whose declared `Type` would COERCE an inherited
@@ -16675,7 +16988,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
16675
16988
  const value = context.parameters[logicalId];
16676
16989
  const paramDef = context.template.Parameters?.[logicalId];
16677
16990
  this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${this.maskSecretsForLog(stringifyParameterForLog(paramDef, value), context)}`);
16678
- this.recordInheritedParameterSecrets(value, context);
16991
+ this.recordInheritedParameterSecrets(logicalId, value, context);
16679
16992
  return value;
16680
16993
  }
16681
16994
  const pseudoValue = await this.resolvePseudoParameter(logicalId, context);
@@ -19696,7 +20009,7 @@ var CloudControlProvider = class {
19696
20009
  await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
19697
20010
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
19698
20011
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
19699
- const { ASGProvider } = await import("./asg-provider-CDfupCgW.js").then((n) => n.n);
20012
+ const { ASGProvider } = await import("./asg-provider-R60Irey3.js").then((n) => n.n);
19700
20013
  return await new ASGProvider().delete(logicalId, physicalId, resourceType, _properties, context);
19701
20014
  }
19702
20015
  const isProtectedEc2Instance = context?.removeProtection === true && resourceType === "AWS::EC2::Instance";
@@ -28528,6 +28841,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
28528
28841
  const current = stateResources[op.logicalId];
28529
28842
  const prev = op.previousState;
28530
28843
  const resolvedPrevProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId) ?? {};
28844
+ recordNestedStackParameterExpressions(secrets, op.resourceType, resolvedPrevProps, prev.properties, STATE_DERIVED_RULES);
28531
28845
  logger.info(` Rollback: Reversing replacement of ${op.logicalId} (${op.resourceType}) — re-creating the old resource and deleting the new one`);
28532
28846
  if (STATEFUL_TYPES.has(op.resourceType)) logger.warn(` ⚠ ${op.logicalId} (${op.resourceType}) is a stateful type — the old physical resource's data was destroyed by the replacement and CANNOT be recovered; the re-created resource starts empty.`);
28533
28847
  const { provider: createProvider } = ctx.providerRegistry.getProviderFor({
@@ -28621,6 +28935,7 @@ async function replaySingle(op, stateResources, stackName, ctx, resolver, orphan
28621
28935
  });
28622
28936
  const desiredProps = await resolveReplayProps(previousState.properties, resolver, secrets, ctx, op.logicalId);
28623
28937
  const currentProps = await resolveReplayProps(current.properties, resolver, secrets, ctx, op.logicalId);
28938
+ recordNestedStackParameterExpressions(secrets, op.resourceType, desiredProps, previousState.properties, STATE_DERIVED_RULES);
28624
28939
  const revertResult = await updateWithRollbackRetry(provider, [
28625
28940
  op.logicalId,
28626
28941
  current.physicalId,
@@ -28773,6 +29088,7 @@ async function replayFailedOperations(failedOps, stateResources, stackName, ctx,
28773
29088
  });
28774
29089
  const desiredProps = await resolveReplayProps(prev.properties, resolver, secrets, ctx, op.logicalId);
28775
29090
  const attemptedProps = await resolveReplayProps(op.attemptedProperties ?? current.properties, resolver, secrets, ctx, op.logicalId);
29091
+ recordNestedStackParameterExpressions(secrets, op.resourceType, desiredProps, prev.properties, STATE_DERIVED_RULES);
28776
29092
  const revertFailedResult = await updateWithRollbackRetry(provider, [
28777
29093
  op.logicalId,
28778
29094
  current.physicalId,
@@ -29715,6 +30031,8 @@ var DeployEngine = class {
29715
30031
  * `this.buildResolverContext({...}, stackName)`.
29716
30032
  */
29717
30033
  buildResolverContext(base, stackName) {
30034
+ const recordedSecretValues = /* @__PURE__ */ new Map();
30035
+ if (this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0) inheritNestedStackParameterAssociations(recordedSecretValues, this.options.inheritedSecrets);
29718
30036
  return {
29719
30037
  template: base.template,
29720
30038
  resources: base.resources,
@@ -29726,7 +30044,7 @@ var DeployEngine = class {
29726
30044
  recordedImports: this.recordedImports,
29727
30045
  recordedOutputReads: this.recordedOutputReads,
29728
30046
  ...this.options.inheritedSecrets && this.options.inheritedSecrets.size > 0 && { inheritedSecrets: this.options.inheritedSecrets },
29729
- recordedSecretValues: /* @__PURE__ */ new Map()
30047
+ recordedSecretValues
29730
30048
  };
29731
30049
  }
29732
30050
  /**
@@ -29753,7 +30071,9 @@ var DeployEngine = class {
29753
30071
  redactParametersForDiff(parameterValues) {
29754
30072
  const inherited = this.options.inheritedSecrets;
29755
30073
  if (!inherited || inherited.size === 0) return parameterValues;
29756
- return redactSecretsForState(parameterValues, inherited);
30074
+ const out = {};
30075
+ for (const [name, value] of Object.entries(parameterValues)) out[name] = inheritedParameterExpression(inherited, name, value) ?? redactSecretsForState(value, inherited);
30076
+ return out;
29757
30077
  }
29758
30078
  /**
29759
30079
  * Redact resolved secret plaintext out of a bag about to be PERSISTED to
@@ -30869,6 +31189,7 @@ var DeployEngine = class {
30869
31189
  const resolvedProps = await this.resolver.resolve(desiredProps, context);
30870
31190
  this.perResourceTemplateProps.set(logicalId, desiredProps);
30871
31191
  const createSecrets = context.recordedSecretValues ?? /* @__PURE__ */ new Map();
31192
+ recordNestedStackParameterExpressions(createSecrets, resourceType, resolvedProps, desiredProps);
30872
31193
  this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
30873
31194
  this.attemptedResolvedProps.set(logicalId, resolvedProps);
30874
31195
  const createDecision = this.providerRegistry.getProviderFor({
@@ -30913,6 +31234,7 @@ var DeployEngine = class {
30913
31234
  this.perResourceSecrets.set(logicalId, updateSecrets);
30914
31235
  const resolvedProps = await this.resolver.resolve(desiredProps, context);
30915
31236
  this.perResourceTemplateProps.set(logicalId, desiredProps);
31237
+ recordNestedStackParameterExpressions(updateSecrets, resourceType, resolvedProps, desiredProps);
30916
31238
  this.auditResolvedAssetReferences(logicalId, resourceType, resolvedProps);
30917
31239
  this.attemptedResolvedProps.set(logicalId, resolvedProps);
30918
31240
  if (JSON.stringify(redactSecretsForState(resolvedProps, updateSecrets, desiredProps)) === JSON.stringify(currentProps)) {
@@ -31581,4 +31903,4 @@ var DeployEngine = class {
31581
31903
 
31582
31904
  //#endregion
31583
31905
  export { maskerOrIdentity as $, CFN_TEMPLATE_URL_LIMIT as $n, redactSecretsForState as $t, renderStatefulReason as A, buildDockerImage as An, ResourceTimeoutError as Ar, classifyReplaySecretRegion as At, exportAliasCollisionScrubWarning as B, synthesisStatusMessage as Bn, isMarkedNonRetryable as Br, describeTypeWithThrottleRetry as Bt, isFinalSnapshotError as C, isCrossRegionRedirect as Cn, LocalMigrateError as Cr, configBooleanRefusal as Ct, extractDeploymentEventError as D, validateContainerRepoName as Dn, NestedStackChildDirectDestroyError as Dr, requireConfigArray as Dt, makeCanonicalizePropertiesFn as E, validateAssetBucketName as En, MissingCdkCliError as Er, replayWarn as Et, green as F, runDockerForeground as Fn, SynthesisError as Fr, s3BucketRegionalDomainName as Ft, IAMRoleProvider as G, resolveCaptureObservedState as Gn, retryClassificationText as Gr, STATE_SOURCED_READBACK_RULES as Gt, secretBearingStateKeyWarning as H, getLegacyStateBucketName as Hn, isThrottlingError as Hr, DagBuilder as Ht, red as I, runDockerStreaming as In, formatError as Ir, s3BucketWebsiteUrl as It, ProviderRegistry as J, resolveStateBucketWithDefaultAndSource as Jn, dynamicReferenceTokens as Jt, collectInlinePolicyNamesManagedBySiblings as K, resolveSkipPrefix as Kn, __exportAll as Kr, TEMPLATE_SOURCED_RULES as Kt, yellow as L, AssetManifestLoader as Ln, isCdkdError as Lr, applyRoleArnIfSet as Lt, bold as M, formatDockerLoginError as Mn, StackHasActiveImportsError as Mr, s3BucketArn as Mt, cyan as N, getDockerCmd as Nn, StackTerminationProtectionError as Nr, s3BucketDomainName as Nt, MULTI_REGION_RECREATE_BLOCKED_TYPES as O, buildDenyExternalAccessPolicy as On, PartialFailureError as Or, requireConfigObject as Ot, gray as P, partitionSensitiveEnv as Pn, StateError as Pr, s3BucketDualStackDomainName as Pt, maskDeep as Q, CFN_TEMPLATE_BODY_LIMIT as Qn, maskSecretsInText as Qt, collectDeclaredOutputNames as R, getDockerImageBySourceHash as Rn, normalizeAwsError as Rr, DiffCalculator as Rt, createPreDeleteFinalSnapshot as S, getBootstrapMarkerKey as Sn, LocalInvokeBuildError as Sr, coerceCfnBoolean as St, unsupportedFinalSnapshotError as T, readBootstrapMarkerBody as Tn, LockError as Tr, readConfigString as Tt, stateKeySecretExposure as U, resolveApp as Un, markNonRetryable as Ur, TemplateParser as Ut, isExportAliasCollision as V, getDefaultStateBucketName as Vn, isRetryableTransientError as Vr, withRetry as Vt, getCurrentResourceSecrets as W, resolveAutoAssetStorage as Wn, markRedactedCause as Wr, STATE_SOURCED_CROSS_GENERATION_RULES as Wt, findSilentDropProperties as X, stateBucketExistenceConfirmed as Xn, isSingleDynamicReferenceToken as Xt, findActionableSilentDrops as Y, resolveUseCdkBootstrapAssets as Yn, errorCauseChain as Yt, createMaskedRetryLogger as Z, warnDeprecatedNoPrefixCliFlag as Zn, maskSecretsInError as Zt, computeImplicitDeleteEdges as _, stripControlChars as _n, ConfigError as _r, refStateLookupFromResource as _t, DeploymentEventsStore as a, exportNamesCarriedFrom as an, canonicalizeRegion as ar, isInterruptedWaitError as at, buildFinalSnapshotIdentifier as b, assertAssetBucketRegion as bn, DeployCancelledError as br, resolveExplicitPhysicalId as bt, replayFailedOperations as c, shouldRetainResource as cn, processStackMessages as cr, slowCcOperationTimeoutMs as ct, updatePartialReason as d, WorkGraph as dn, AwsClients as dr, IntrinsicFunctionResolver as dt, scrubResourceRecord as en, MIGRATE_TMP_PREFIX as er, CUSTOM_RESOURCE_RESPONSE_PREFIX as et, UNSPECIFIED_SKIP_REASON as f, buildAssetRedirectMap as fn, getAwsClients as fr, carriesDynamicReference as ft, IMPLICIT_DELETE_DEPENDENCIES as g, escapeRegExp$1 as gn, CdkdError as gr, parameterTypeMayLoseSecretIdentity as gt, maskingRetryLogger as h, rewriteTemplateAssetReferences as hn, AssetError as hr, isUnboundTemplateParameter as ht, DeploymentEventsReader as i, rebuildClientForBucketRegion as in, PARTITION_TABLE as ir, interruptWatchListenerCount as it, formatResourceLine as j, dockerSpawnEnvWithSensitive as jn, ResourceUpdateNotSupportedError as jr, producerRegionsFromState as jt, isStatefulRecreateTargetSync as k, describeAwsFailure as kn, ProvisioningError as kr, requireConfigString as kt, replayRollback as l, AssetPublisher as ln, clearBucketRegionCache as lr, disableInstanceApiTermination as lt, withResourceDeadline as m, loadPublishableAssetManifest as mn, setAwsClients as mr, getAccountInfo as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, displaySafe as nn, uploadCfnTemplate as nr, beginCommandInterruptScope as nt, planFailedOps as o, importableOutputKeys as on, derivePartitionAndUrlSuffix as or, startInterruptWatch as ot, deleteSkipReason as p, createAssetRedirectResolver as pn, resetAwsClients as pr, cfnRefValueFromPhysicalId as pt, clearOnUpdateRemoval as q, resolveStateBucketWithDefault as qn, createSecretMasker as qt, DeployEngine as r, S3StateBackend as rn, expectedOwnerParam as rr, endCommandInterruptScope as rt, planRollback as s, importableOutputs as sn, AssemblyReader as sr, CloudControlProvider as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, LockManager as tn, findLargeInlineResources as tr, DEFAULT_STATE_PREFIX as tt, updatePartialMessage as u, stringifyValue as un, resolveBucketRegion as ur, isTerminationProtectionPropagationError as ut, ATOMIC_FINAL_SNAPSHOT_TYPES as v, AssetModeResolver as vn, CrossAccountSecretRefusalError as vr, WAFv2WebACLProvider as vt, refusesFinalSnapshot as w, parseBootstrapMarker as wn, LocalStartServiceError as wr, configStringRefusal as wt, ccRoutedFinalSnapshotError as x, ensureAssetStorage as xn, DynamicReferenceRegionAmbiguousError as xr, assertRegionMatch as xt, PRE_DELETE_SNAPSHOT_TYPES as y, BOOTSTRAP_MARKER_PREFIX as yn, DependencyError as yr, normalizeAwsTagsToCfn as yt, collectPublishedOutputNames as z, Synthesizer as zn, withErrorHandling as zr, INTRINSIC_KEYS as zt };
31584
- //# sourceMappingURL=deploy-engine-Ce3payPI.js.map
31906
+ //# sourceMappingURL=deploy-engine-Cvkqa30x.js.map