@go-to-k/cdkd 0.290.4 → 0.290.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { b as displayIdent, d as applyDefaultNameForFallback, f as explicitNamePropertyFor, g as looksLikeCdkdGeneratedName, h as getCurrentStackName, m as generateResourceNameWithFallback, n as getLogger, p as generateResourceName, r as isStdoutReservedForPayload, s as getLiveRenderer, v as withStackName, x as displaySafe, y as UNRENDERABLE } from "./logger-th1myfgh.js";
2
2
  import { t as awsClientDefaults } from "./aws-client-defaults-D-iYiIJ6.js";
3
- import { t as getCdkdVersion } from "./version-BUcQz4SU.js";
3
+ import { t as getCdkdVersion } from "./version-BvSdNpMz.js";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { CreateBucketCommand, DeleteObjectCommand, DeleteObjectsCommand, GetBucketLocationCommand, GetBucketReplicationCommand, GetObjectCommand, HeadBucketCommand, HeadObjectCommand, ListObjectVersionsCommand, ListObjectsV2Command, NoSuchKey, PutBucketEncryptionCommand, PutBucketPolicyCommand, PutObjectCommand, PutPublicAccessBlockCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
@@ -7218,6 +7218,94 @@ function describeDockerFailure(error, args) {
7218
7218
  return redactDockerArgvInText(capturedStreamText(error, "stderr") || thrownMessageText(error) || safeStringify(error), args);
7219
7219
  }
7220
7220
  /**
7221
+ * A `cause` that is safe to CHAIN onto a wrapper error, for a caller that
7222
+ * wants the retry classifiers to see the failure as an object rather than as
7223
+ * an interpolated string (issue go-to-k/cdkd#2075).
7224
+ *
7225
+ * Attaching the raw `execFile` error reopens the channel go-to-k/cdkd#2440
7226
+ * closed. The mandate above applies to every docker failure text this module
7227
+ * hands out, "including one whose argv carries no user data today" — and a
7228
+ * chained cause IS such a text: `formatError` prints `cause.message`, and the
7229
+ * CLI's top-level handler walks the whole chain. The raw message is the
7230
+ * command line, so the moment an argv gains a `-e` or a `--build-arg` the
7231
+ * secret is in a printed line again, from a call site nobody re-reads.
7232
+ *
7233
+ * So the returned cause carries the REDACTED description as its message, and
7234
+ * chains nothing further. What the classifiers read is copied across — but
7235
+ * through an ALLOWLIST ({@link CLASSIFICATION_FIELDS}), never by sweeping the
7236
+ * original's own keys.
7237
+ *
7238
+ * The allowlist is the whole point, and a denylist was the first cut: it
7239
+ * skipped `message` / `stack` / `cause` and copied the rest, which carried
7240
+ * `err.cmd` — the full command line — onto the new error verbatim. That is
7241
+ * the SAME field that defeated four rounds of the go-to-k/cdkd#2440 fence
7242
+ * (`.claude/rules/docker-argv-redaction.md`), reintroduced inside the function
7243
+ * written to close it. `stderr` / `stdout` are excluded for the same reason:
7244
+ * the composer above has already read and REDACTED them into the message, so
7245
+ * copying the raw streams would hand back exactly what it removed.
7246
+ *
7247
+ * Fail-closed is the correct direction here for the reason the cache-param
7248
+ * allowlist gives: a field missing from the allowlist costs one degraded retry
7249
+ * classification, while a field missing from a denylist costs a printed
7250
+ * credential.
7251
+ */
7252
+ /**
7253
+ * The fields {@link redactedDockerCause} carries from the original error onto
7254
+ * the redacted one.
7255
+ *
7256
+ * WHAT EACH HALF IS FOR, stated precisely because an earlier revision of this
7257
+ * comment got it wrong in a way that reads as more than it is. The AWS-shaped
7258
+ * entries (`$metadata`, `$fault`, `__type`, `code`, `statusCode`) are what
7259
+ * `isThrottlingError` / `isTransientServerError` /
7260
+ * `describeRetryClassificationSignals` actually consult, and they are reachable
7261
+ * here only when an SDK error is the cause -- the ECR-login path, whose
7262
+ * `GetAuthorizationToken` failure can be one.
7263
+ *
7264
+ * `exitCode` is NOT read by any classifier. It is carried because it is the
7265
+ * ONLY field the DOMINANT failure has: these four sites reject through
7266
+ * `spawnStreaming`, whose `SpawnError` sets `stderr` / `stdout` / `exitCode`
7267
+ * and nothing else, so without it a non-zero docker exit produced a cause with
7268
+ * a name and a message and no other content at all. What that buys is the
7269
+ * DIAGNOSTIC -- `cli/index.ts` renders the chain's own enumerable props -- not
7270
+ * a retry decision. Do not restate it as a classification field.
7271
+ *
7272
+ * `stderr` / `stdout` stay OFF: the composer has already read and REDACTED
7273
+ * them into the message, so copying the raw streams would hand back exactly
7274
+ * what it removed.
7275
+ */
7276
+ const CLASSIFICATION_FIELDS = [
7277
+ "exitCode",
7278
+ "code",
7279
+ "errno",
7280
+ "signal",
7281
+ "status",
7282
+ "statusCode",
7283
+ "$metadata",
7284
+ "$fault",
7285
+ "__type"
7286
+ ];
7287
+ function redactedDockerCause(error, args) {
7288
+ if (!(error instanceof Error)) return void 0;
7289
+ const cause = new Error(describeDockerFailure(error, args));
7290
+ try {
7291
+ cause.name = error.name;
7292
+ } catch {}
7293
+ const NON_RETRYABLE_MARKER = Symbol.for("cdkd.nonRetryable");
7294
+ try {
7295
+ if (error[NON_RETRYABLE_MARKER] === true) Object.defineProperty(cause, NON_RETRYABLE_MARKER, {
7296
+ value: true,
7297
+ enumerable: false,
7298
+ configurable: true
7299
+ });
7300
+ } catch {}
7301
+ const source = error;
7302
+ const target = cause;
7303
+ for (const key of CLASSIFICATION_FIELDS) try {
7304
+ if (key in source) target[key] = source[key];
7305
+ } catch {}
7306
+ return cause;
7307
+ }
7308
+ /**
7221
7309
  * Composer for a captured-output failure where the diagnostic may be on
7222
7310
  * STDOUT rather than stderr (`runDockerStreaming`'s non-zero-exit path, whose
7223
7311
  * `SpawnError` carries both). `fallback` is used when neither stream said
@@ -7474,7 +7562,12 @@ var DockerAssetPublisher = class {
7474
7562
  try {
7475
7563
  await this.tagImage(actualTag, tag);
7476
7564
  } catch (err) {
7477
- throw new AssetError(`Docker tag failed re-tagging '${actualTag}' → '${tag}': ${err.message ?? String(err)}`);
7565
+ const e = err;
7566
+ throw new AssetError(`Docker tag failed re-tagging '${actualTag}' → '${tag}': ${e.message ?? String(err)}`, e.cause instanceof Error ? e.cause : redactedDockerCause(err, [
7567
+ "tag",
7568
+ actualTag,
7569
+ tag
7570
+ ]));
7478
7571
  }
7479
7572
  }
7480
7573
  }
@@ -7549,7 +7642,7 @@ var DockerAssetPublisher = class {
7549
7642
  await runDockerStreaming(loginArgs, { input: password });
7550
7643
  loggedInRegistries.add(registryKey);
7551
7644
  } catch (err) {
7552
- throw new AssetError(`ECR login failed: ${formatDockerLoginError(describeDockerFailure(err, loginArgs), endpoint)}`);
7645
+ throw new AssetError(`ECR login failed: ${formatDockerLoginError(describeDockerFailure(err, loginArgs), endpoint)}`, redactedDockerCause(err, loginArgs));
7553
7646
  }
7554
7647
  }
7555
7648
  /**
@@ -7564,7 +7657,7 @@ var DockerAssetPublisher = class {
7564
7657
  try {
7565
7658
  await runDockerStreaming(tagArgs);
7566
7659
  } catch (err) {
7567
- throw new AssetError(`Docker tag failed: ${describeDockerFailure(err, tagArgs)}`);
7660
+ throw new AssetError(`Docker tag failed: ${describeDockerFailure(err, tagArgs)}`, redactedDockerCause(err, tagArgs));
7568
7661
  }
7569
7662
  }
7570
7663
  /**
@@ -7578,7 +7671,7 @@ var DockerAssetPublisher = class {
7578
7671
  try {
7579
7672
  await runDockerStreaming(pushArgs);
7580
7673
  } catch (err) {
7581
- throw new AssetError(`Docker push failed: ${describeDockerFailure(err, pushArgs)}`);
7674
+ throw new AssetError(`Docker push failed: ${describeDockerFailure(err, pushArgs)}`, redactedDockerCause(err, pushArgs));
7582
7675
  }
7583
7676
  }
7584
7677
  /**
@@ -9164,9 +9257,60 @@ function safeJournalText(value) {
9164
9257
  return displaySafe(value, { asciiOnly: true }) || "<unrenderable>";
9165
9258
  }
9166
9259
  /**
9260
+ * The per-operation shape check (issue #3140). The journal is an unchecked
9261
+ * cast beyond `journalVersion` and `segments` being an array, and the
9262
+ * executor keys EVERY lookup on `op.logicalId` -- `stateResources[..]`,
9263
+ * `orphanLogicalIds.has(..)`, the events it records -- so a non-string id
9264
+ * planted in the bucket either coerces at each lookup (`123` finds the
9265
+ * record named `'123'`) or throws a raw `TypeError` at the first one (an
9266
+ * object whose `toString` is not callable, the issue #2947 shape). Refused
9267
+ * HERE, once, so no downstream reader has to remember; the executor keeps its
9268
+ * own `typeof` guard on the one pasted command as defense-in-depth, because
9269
+ * the deploy engine's in-process rollback reaches it without this parser.
9270
+ *
9271
+ * Only the fields a lookup or a route is keyed on: `logicalId` (non-empty
9272
+ * string), `resourceType` (string), `changeType` (string -- an unknown value
9273
+ * lands on the executor's UPDATE arm by design, pinned by a case there), and
9274
+ * `physicalId` when present (string). `previousState` and the rest stay as
9275
+ * they were; a bad value there fails the op it belongs to, not the parse.
9276
+ *
9277
+ * The refusal names the INDEX and the TYPE, never the value -- there is
9278
+ * nothing to sanitize, so nothing to get wrong.
9279
+ */
9280
+ function refuseMalformedOperation(shownStack, where, op) {
9281
+ const fail = (field, value, expected) => refuseMalformed(shownStack, `${where}.${field} must be ${expected} (got ${kind(value)}).`);
9282
+ if (typeof op !== "object" || op === null || Array.isArray(op)) refuseMalformed(shownStack, `${where} must be an object (got ${kind(op)}).`);
9283
+ const o = op;
9284
+ if (typeof o["logicalId"] !== "string" || o["logicalId"].length === 0) fail("logicalId", o["logicalId"], "a non-empty string");
9285
+ if (typeof o["resourceType"] !== "string") fail("resourceType", o["resourceType"], "a string");
9286
+ if (typeof o["changeType"] !== "string") fail("changeType", o["changeType"], "a string");
9287
+ if (o["physicalId"] !== void 0 && typeof o["physicalId"] !== "string") fail("physicalId", o["physicalId"], "a string when present");
9288
+ }
9289
+ /** The TYPE of a JSON-derived value, for a refusal that must not echo the value. */
9290
+ function kind(v) {
9291
+ return v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
9292
+ }
9293
+ /**
9294
+ * The one spelling of a malformed-journal refusal. `detail` is built from
9295
+ * indices, field names and `kind()` words only -- never a journal value -- and
9296
+ * every refusal names the way out, as `UnknownRollbackJournalVersionError`
9297
+ * names `Upgrade cdkd`: the journal is a sibling of `state.json`, so removing
9298
+ * that one object discards it (`cdkd destroy` sweeps it as well), and no
9299
+ * other stack is affected because `cdkd rollback` never parses another
9300
+ * stack's journal. The same text reaches the deploy engine's `Failed to write
9301
+ * rollback journal` warning, since appending a segment parses the existing
9302
+ * journal first.
9303
+ */
9304
+ function refuseMalformed(shownStack, detail) {
9305
+ throw new Error(`Rollback journal for '${shownStack}' is malformed: ${detail} Remove the stack's rollback-journal.json (next to its state.json) to discard it.`);
9306
+ }
9307
+ /**
9167
9308
  * Parse + validate a journal body. Throws
9168
9309
  * {@link UnknownRollbackJournalVersionError} on a newer version, and a plain
9169
- * Error on a structurally-invalid body.
9310
+ * Error on a structurally-invalid body -- including, since issue #3140, a
9311
+ * segment that is not an object, an `operations` that is not an array, and an
9312
+ * operation whose `logicalId` / `resourceType` / `changeType` / `physicalId`
9313
+ * is not the string the executor keys on (see `refuseMalformedOperation`).
9170
9314
  */
9171
9315
  function parseRollbackJournal(bodyString, stackName) {
9172
9316
  const shownStack = safeJournalText(stackName);
@@ -9186,6 +9330,16 @@ function parseRollbackJournal(bodyString, stackName) {
9186
9330
  }
9187
9331
  if (j.journalVersion > 1) throw new UnknownRollbackJournalVersionError(j.journalVersion, stackName);
9188
9332
  if (!Array.isArray(j.segments)) throw new Error(`Rollback journal for '${shownStack}' is missing a 'segments' array.`);
9333
+ j.segments.forEach((segment, s) => {
9334
+ if (typeof segment !== "object" || segment === null || Array.isArray(segment)) refuseMalformed(shownStack, `segments[${s}] must be an object (got ${kind(segment)}).`);
9335
+ const seg = segment;
9336
+ if (!Array.isArray(seg["operations"])) refuseMalformed(shownStack, `segments[${s}].operations must be an array (got ${kind(seg["operations"])}).`);
9337
+ seg["operations"].forEach((op, i) => refuseMalformedOperation(shownStack, `segments[${s}].operations[${i}]`, op));
9338
+ if (seg["failedOperations"] !== void 0) {
9339
+ if (!Array.isArray(seg["failedOperations"])) refuseMalformed(shownStack, `segments[${s}].failedOperations must be an array when present (got ${kind(seg["failedOperations"])}).`);
9340
+ seg["failedOperations"].forEach((op, i) => refuseMalformedOperation(shownStack, `segments[${s}].failedOperations[${i}]`, op));
9341
+ }
9342
+ });
9189
9343
  return {
9190
9344
  journalVersion: j.journalVersion,
9191
9345
  stackName: j.stackName ?? stackName,
@@ -9618,7 +9772,7 @@ var S3StateBackend = class {
9618
9772
  }));
9619
9773
  } catch (error) {
9620
9774
  const name = error.name;
9621
- if (name === "NotFound" || name === "NoSuchBucket") throw new StateError(`State bucket '${this.config.bucket}' does not exist. Run 'cdkd bootstrap' to create it, or specify an existing bucket via --state-bucket, CDKD_STATE_BUCKET, or cdk.json context.cdkd.stateBucket.`);
9775
+ if (name === "NotFound" || name === "NoSuchBucket") throw new StateError(`State bucket '${this.config.bucket}' does not exist. Run 'cdkd bootstrap' to create it, or specify an existing bucket via --state-bucket, CDKD_STATE_BUCKET, or cdk.json context.cdkd.stateBucket.`, error instanceof Error ? error : void 0);
9622
9776
  const normalized = normalizeAwsError(error, {
9623
9777
  bucket: this.config.bucket,
9624
9778
  operation: "HeadBucket"
@@ -9750,7 +9904,7 @@ var S3StateBackend = class {
9750
9904
  }
9751
9905
  return response.ETag;
9752
9906
  } catch (error) {
9753
- if (error.name === "PreconditionFailed") throw new StateError(`State has been modified by another process. Expected ETag: ${expectedEtag}, but state has changed.`);
9907
+ if (error.name === "PreconditionFailed") throw new StateError(`State has been modified by another process. Expected ETag: ${expectedEtag}, but state has changed.`, error instanceof Error ? error : void 0);
9754
9908
  const normalized = normalizeAwsError(error, {
9755
9909
  bucket: this.config.bucket,
9756
9910
  operation: "PutObject"
@@ -13148,7 +13302,11 @@ const UNKNOWN_PART_PLACEHOLDER = "\0";
13148
13302
  * longer equals the leaf's text around the middle, so the arm refuses) -- and,
13149
13303
  * through the frame arm's rewrite, for the nested-stack parameter recorder's
13150
13304
  * `frameSpellingOf`, which carries a framed parameter to the child only when
13151
- * that arm wrote it. The recorder's own rendering of such a part does not
13305
+ * the position pass rewrote it -- which, on a value the recorder's gate (ii)
13306
+ * finds the scan silent on, only the frame arm can do (the value scan, the
13307
+ * skeleton arm and the cross-stack arm each rewrite an object-sourced leaf
13308
+ * whose WHOLE value is a recorded plaintext, and (ii) is what refuses that
13309
+ * value). The recorder's own rendering of such a part does not
13152
13310
  * decide the case: a part outside the token leaves its one span and service
13153
13311
  * prefix the same either way.
13154
13312
  *
@@ -13422,8 +13580,15 @@ function positionByCrossStackSource(bag, source, secrets) {
13422
13580
  * the source leaf is an `Fn::Join` / `Fn::Sub` there is none, so the leaf fell
13423
13581
  * to the value scan — and the value map is keyed by the resolved PLAINTEXT, so
13424
13582
  * a colliding pair collapses there exactly as it did before #1904. That is the
13425
- * DOMINANT CDK shape rather than an edge case: any secret reached through an L2
13426
- * token renders the secret ARN as a `Ref`, hence an `Fn::Join`.
13583
+ * DOMINANT CDK shape rather than an edge case: a secret reached through an L2
13584
+ * token usually renders the secret ARN as a `Ref`, hence an `Fn::Join`. The
13585
+ * exceptions render a plain `{{resolve:...}}` string, which the literal arm
13586
+ * already positions: a secret imported by a LITERAL ARN
13587
+ * (`Secret.fromSecretCompleteArn` / `fromSecretPartialArn`, whose
13588
+ * `secretValue` and `secretValueFromJson` both inline it),
13589
+ * `SecretValue.secretsManager('<literal arn>')` and
13590
+ * `SecretValue.ssmSecure(...)` (synth-probed on this repo's aws-cdk-lib,
13591
+ * issue #3143; `new Secret(...)` and `fromSecretNameV2` are the join).
13427
13592
  *
13428
13593
  * Three conditions must ALL hold before an expression is persisted, and each
13429
13594
  * removes a different way of being wrong:
@@ -23291,15 +23456,19 @@ function withoutProducerRegions(context) {
23291
23456
  }
23292
23457
  /**
23293
23458
  * Masked log twins registered this pass, per pass bag (issue #3100;
23294
- * `rememberLogTwin` / `logTwinOfProduct`). Log-only: nothing that resolves or
23295
- * persists a value reads it.
23459
+ * `rememberLogTwin` / `logTwinOfProduct`). Nothing that RESOLVES a value reads
23460
+ * it. One persistence decision does: `resolveBase64` registers its encoding
23461
+ * mask-only when the input's position mask fires (issue #3119), which a nested
23462
+ * child also reaches through the inherited-bag lookup (issue #3114).
23296
23463
  *
23297
23464
  * MODULE scope, not instance scope: a region-pinned sibling resolver
23298
23465
  * (`resolverForProducerRegion`) resolves on behalf of the consumer with the
23299
23466
  * consumer's bag, so an instance-local registry left the sibling's twin where
23300
23467
  * the consumer's `Fn::Join` / `Fn::Sub` never looked. The key is still the
23301
- * pass's own bag object, so scope does not widen: another pass holds another
23302
- * bag and cannot reach these entries, and they die with the bag.
23468
+ * pass's own bag object, so scope does not widen to unrelated passes: a pass
23469
+ * reaches only the entries under its own bag and under the bag it was handed
23470
+ * as `inheritedSecrets` (a nested-stack child reading its parent's, issue
23471
+ * #3114), and the entries die with the bag.
23303
23472
  */
23304
23473
  const LOG_TWINS_BY_PASS = /* @__PURE__ */ new WeakMap();
23305
23474
  /**
@@ -23430,20 +23599,22 @@ const recordedSecretExpressions = {
23430
23599
  */
23431
23600
  const cachedEc2InstanceAttributes = Object.create(null);
23432
23601
  /**
23433
- * The two sibling caches of {@link cachedEc2InstanceAttributes} (issue
23434
- * #3096), each holding a value the resolver read LIVE because the state
23435
- * record omitted it — `definedAttributes` drops a member the provider could
23436
- * not read back (issue #3077), so the `Fn::GetAtt` falls out of the flat
23437
- * lookup and into `constructAttribute`'s per-type arm. Keyed by physical id
23438
- * (AWS-minted, unique across accounts and regions, and the value — a default
23439
- * security group id, a distribution hostname — never changes); like the
23440
- * instance cache, a refusal caches nothing. Same process lifetime, cleared
23441
- * together with it by {@link resetAccountInfoCache}. The RDS `DBProxy` / `DBProxyEndpoint`
23442
- * `VpcId` arms have no cache because they have no live read: `AwsClients`
23443
- * exposes no RDS client and this file imports none, so those two arms refuse
23444
- * outright (see `refuseUnservedAttribute`'s callers).
23445
- *
23446
- * All three are NULL-PROTOTYPE objects (#3096 delta review, measured): the
23602
+ * The three sibling caches of {@link cachedEc2InstanceAttributes} (issues
23603
+ * #3096 / #3097), each holding a value the resolver read LIVE because the
23604
+ * state record omitted it — `definedAttributes` drops a member the provider
23605
+ * could not read back (issue #3077), so the `Fn::GetAtt` falls out of the
23606
+ * flat lookup and into `constructAttribute`'s per-type arm. Keyed by physical
23607
+ * id: the read is region-pinned (`clientsForRegion(this.explicitRegion)`),
23608
+ * so the key names one resource in that region, and the value — a default
23609
+ * security group id, a distribution hostname, a security group's VPC —
23610
+ * never changes; like the instance cache, a refusal caches nothing. Same
23611
+ * process lifetime, cleared together with it by {@link resetAccountInfoCache}.
23612
+ * The RDS `DBProxy` / `DBProxyEndpoint` `VpcId` arms have no cache because
23613
+ * they have no live read: `AwsClients` exposes no RDS client and this file
23614
+ * imports none, so those two arms refuse outright (see
23615
+ * `refuseUnservedAttribute`'s callers).
23616
+ *
23617
+ * All four are NULL-PROTOTYPE objects (#3096 delta review, measured): the
23447
23618
  * key is a state-record physical id, and on a plain `{}` a record holding
23448
23619
  * `constructor` / `__proto__` / `hasOwnProperty` read a FUNCTION out of
23449
23620
  * `Object.prototype` as the cached value — served with zero AWS calls, ahead
@@ -23452,6 +23623,7 @@ const cachedEc2InstanceAttributes = Object.create(null);
23452
23623
  */
23453
23624
  const cachedVpcDefaultSecurityGroups = Object.create(null);
23454
23625
  const cachedCloudFrontDomainNames = Object.create(null);
23626
+ const cachedSecurityGroupVpcIds = Object.create(null);
23455
23627
  /**
23456
23628
  * The region this call answers for: the caller's override, else the ambient one.
23457
23629
  *
@@ -23900,6 +24072,29 @@ async function resolveAccountIdentity() {
23900
24072
  }
23901
24073
  }
23902
24074
  /**
24075
+ * Is a STORED `''` for this attribute a value the resource can never have —
24076
+ * so the flat lookup must read it as ABSENT and let the live arm run?
24077
+ *
24078
+ * Exactly one attribute qualifies today (issue #3097 review): an
24079
+ * `AWS::EC2::SecurityGroup`'s `VpcId`. Every security group lives in a VPC
24080
+ * (EC2-Classic retired 2022-08-15), so `''` can only be the pre-#3097
24081
+ * provider's copy of a template that declared no `VpcId` — and that record is
24082
+ * rewritten only by an `update()`, which a no-change deploy never issues, so
24083
+ * without this carve-out the stored `''` shadows the live arm for the life of
24084
+ * the record. The record itself stays `''` until the next update; only the
24085
+ * RESOLUTION changes.
24086
+ *
24087
+ * Deliberately NOT a general "`''` means absent" rule: #3077 records a settled
24088
+ * EC2 instance's missing public address as the KNOWN empty `''`
24089
+ * (CloudFormation's own answer), and that one must keep being served from
24090
+ * state rather than sent back to `DescribeInstances` on every resolution.
24091
+ * A new entry here needs the same argument — that `''` is IMPOSSIBLE for the
24092
+ * attribute, not merely unlikely.
24093
+ */
24094
+ function isImpossibleEmptyStoredAttribute(resourceType, attributeName, storedValue) {
24095
+ return storedValue === "" && resourceType === "AWS::EC2::SecurityGroup" && attributeName === "VpcId";
24096
+ }
24097
+ /**
23903
24098
  * Does a constructed `Fn::GetAtt` answer embed the placeholder account id?
23904
24099
  *
23905
24100
  * The guard in `constructGuardedAttribute` used to test
@@ -24625,6 +24820,11 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
24625
24820
  async resolveParameters(template, userParameters, options) {
24626
24821
  const inheritedSecrets = options?.inheritedSecrets;
24627
24822
  const maskInherited = (text) => inheritedSecrets && inheritedSecrets.size > 0 ? maskSecretsInText(text, inheritedSecrets) : text;
24823
+ const inheritedLogContext = {
24824
+ template,
24825
+ resources: {},
24826
+ ...inheritedSecrets && { inheritedSecrets }
24827
+ };
24628
24828
  const parameters = Object.create(null);
24629
24829
  const templateParameters = template.Parameters;
24630
24830
  if (!templateParameters || typeof templateParameters !== "object") return parameters;
@@ -24637,7 +24837,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
24637
24837
  if (userValue !== void 0) {
24638
24838
  this.refuseCoercedInheritedSecret(name, paramDef, userValue, inheritedSecrets);
24639
24839
  parameters[name] = this.coerceParameterValue(userValue, paramDef.Type);
24640
- this.logger.debug(`Parameter ${name}: using user-provided value ${maskInherited(stringifyParameterForLog(paramDef, userValue))}`);
24840
+ this.logger.debug(`Parameter ${name}: using user-provided value ${maskInherited(stringifyParameterForLog(paramDef, this.maskValueLeaves(userValue, inheritedLogContext)))}`);
24641
24841
  continue;
24642
24842
  }
24643
24843
  }
@@ -24966,7 +25166,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
24966
25166
  const value = context.parameters[logicalId];
24967
25167
  const declared = context.template.Parameters;
24968
25168
  const paramDef = declared != null && Object.hasOwn(declared, logicalId) ? declared[logicalId] : void 0;
24969
- this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${this.maskSecretsForLog(stringifyParameterForLog(paramDef, value), context)}`);
25169
+ this.logger.debug(`Resolved Ref to parameter: ${logicalId} -> ${this.maskSecretsForLog(stringifyParameterForLog(paramDef, this.maskValueLeaves(value, context)), context)}`);
24970
25170
  this.recordInheritedParameterSecrets(logicalId, value, context);
24971
25171
  return value;
24972
25172
  }
@@ -25133,15 +25333,16 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25133
25333
  const resource = Object.hasOwn(context.resources, logicalId) ? context.resources[logicalId] : void 0;
25134
25334
  if (!resource) throw markNonRetryable(/* @__PURE__ */ new Error(`Resource ${logicalId} not found for Fn::GetAtt`));
25135
25335
  if (!(resource.resourceType === "AWS::EC2::VPC" && attributeName === "Ipv6CidrBlocks") && resource.attributes !== void 0) {
25136
- const flatValue = Object.hasOwn(resource.attributes, attributeName) ? resource.attributes[attributeName] : void 0;
25336
+ const storedValue = Object.hasOwn(resource.attributes, attributeName) ? resource.attributes[attributeName] : void 0;
25337
+ const flatValue = isImpossibleEmptyStoredAttribute(resource.resourceType, attributeName, storedValue) ? void 0 : storedValue;
25137
25338
  if (flatValue !== void 0) {
25138
25339
  this.rejectPlaceholderArnAttribute(resource, attributeName, flatValue, logicalId, context);
25139
25340
  if (resource.resourceType === "AWS::Route53::HostedZone" && attributeName === "NameServers" && typeof flatValue === "string") {
25140
25341
  const nameServers = flatValue === "" ? [] : flatValue.split(",");
25141
- this.logger.debug(`Normalized legacy Fn::GetAtt attribute: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, nameServers), context)}`);
25342
+ this.logger.debug(`Normalized legacy Fn::GetAtt attribute: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, this.maskValueLeaves(nameServers, context)), context)}`);
25142
25343
  return this.noteAttributeSecrecy(logicalId, attributeName, nameServers, context);
25143
25344
  }
25144
- this.logger.debug(`Resolved Fn::GetAtt from attributes: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, flatValue), context)}`);
25345
+ this.logger.debug(`Resolved Fn::GetAtt from attributes: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, this.maskValueLeaves(flatValue, context)), context)}`);
25145
25346
  if (resource.resourceType === NESTED_STACK_RESOURCE_TYPE$1 && attributeName.startsWith(NESTED_STACK_OUTPUT_ATTRIBUTE_PREFIX) && carriesDynamicReference(flatValue)) return await this.reresolveCrossStackValue(flatValue, nestedStackChildRegionFromLocalArn(resource.physicalId), context, `nested stack ${logicalId} ${attributeName}`, crossStackSourceKey({ "Fn::GetAtt": getAtt }));
25146
25347
  return this.noteAttributeSecrecy(logicalId, attributeName, flatValue, context);
25147
25348
  }
@@ -25154,7 +25355,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25154
25355
  break;
25155
25356
  }
25156
25357
  if (cursor !== void 0) {
25157
- this.logger.debug(`Resolved Fn::GetAtt from nested attributes: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, cursor), context)}`);
25358
+ this.logger.debug(`Resolved Fn::GetAtt from nested attributes: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, this.maskValueLeaves(cursor, context)), context)}`);
25158
25359
  return this.noteAttributeSecrecy(logicalId, attributeName, cursor, context);
25159
25360
  }
25160
25361
  }
@@ -25164,7 +25365,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25164
25365
  throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, ${this.maskSecretsForLog(attributeName, context)}]: the nested stack '${logicalId}' declares no output named '${this.maskSecretsForLog(attributeName.slice(8), context)}'. Its outputs are ${declared.length > 0 ? declared.join(", ") : "(none)"}. Check the output name in the nested stack's template, and deploy the child stack again if you have just added it.`));
25165
25366
  }
25166
25367
  const value = await this.constructGuardedAttribute(resource, attributeName, context, logicalId);
25167
- this.logger.debug(`Resolved Fn::GetAtt: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, value), context)}`);
25368
+ this.logger.debug(`Resolved Fn::GetAtt: ${logicalId}.${this.maskSecretsForLog(attributeName, context)} -> ${this.maskSecretsForLog(stringifyAttributeForLog(attributeName, this.maskValueLeaves(value, context)), context)}`);
25168
25369
  return value;
25169
25370
  }
25170
25371
  /**
@@ -25578,7 +25779,38 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25578
25779
  }
25579
25780
  if (resourceType === "AWS::EC2::SecurityGroup") switch (attributeName) {
25580
25781
  case "GroupId": return physicalId;
25581
- case "VpcId": return;
25782
+ case "VpcId": {
25783
+ if (!/^sg-[0-9a-f]+$/.test(physicalId)) throw markNonRetryable(new IntrinsicResolutionRefusalError(`Cannot resolve Fn::GetAtt [${logicalId}, VpcId] for AWS::EC2::SecurityGroup: the state record's physical id "${displaySafe(this.maskThenStripThenMask(physicalId, context)).slice(0, 64)}" is not a security group id (sg-<hex>), so cdkd will not look it up. Repair the record (cdkd import, or re-create the security group) and deploy again.`));
25784
+ const cachedVpcId = cachedSecurityGroupVpcIds[physicalId];
25785
+ if (cachedVpcId !== void 0) return cachedVpcId;
25786
+ let vpcId;
25787
+ try {
25788
+ vpcId = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeSecurityGroupsCommand({ GroupIds: [physicalId] }))).SecurityGroups?.[0]?.VpcId;
25789
+ } catch (err) {
25790
+ this.refuseUnservedAttribute({
25791
+ logicalId,
25792
+ attributeName,
25793
+ resourceType,
25794
+ physicalId,
25795
+ context,
25796
+ observed: this.describeFailureObserved("DescribeSecurityGroups", err, context),
25797
+ remedy: "Fix the read (the ec2:DescribeSecurityGroups permission, or the region) and deploy again."
25798
+ });
25799
+ }
25800
+ if (vpcId) {
25801
+ cachedSecurityGroupVpcIds[physicalId] = vpcId;
25802
+ return vpcId;
25803
+ }
25804
+ this.refuseUnservedAttribute({
25805
+ logicalId,
25806
+ attributeName,
25807
+ resourceType,
25808
+ physicalId,
25809
+ context,
25810
+ observed: "DescribeSecurityGroups reports no VpcId for the group",
25811
+ remedy: "Check the security group in the console, or reference the attribute from a later deploy."
25812
+ });
25813
+ }
25582
25814
  default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25583
25815
  }
25584
25816
  if (resourceType === "AWS::EC2::Subnet") switch (attributeName) {
@@ -25716,9 +25948,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25716
25948
  * belongs, a `vpc-...` in a security-group slot, a distribution id where a
25717
25949
  * hostname belongs, a proxy name in a `VpcId` position. Loud only where AWS
25718
25950
  * rejects the shape; an Output, an export or a free-text property carried it
25719
- * silently. The physical id can never be the value for these attributes,
25720
- * so the honest answer is a refusal naming the resource, the attribute, what
25721
- * was observed and the remedy.
25951
+ * silently. The security group `VpcId` arm (issue #3097) joined with the
25952
+ * opposite pre-fix defect — it answered `undefined`, which `Fn::Join`
25953
+ * renders `'undefined'` — and takes the same refusal. The physical id can
25954
+ * never be the value for these attributes, so the honest answer is a
25955
+ * refusal naming the resource, the attribute, what was observed and the
25956
+ * remedy.
25722
25957
  *
25723
25958
  * DELIBERATELY NOT `markNonRetryable`, unlike every other refusal in this
25724
25959
  * file but the fabricated-account guard: the verdict is TIME-DEPENDENT. A
@@ -25734,7 +25969,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25734
25969
  * interpolated hole happens to carry a pattern — the `logicalId` (the #1838
25735
25970
  * hazard: `DependencyViolation` and two other bare words), or the error
25736
25971
  * CLASS name `describeFailureObserved` puts in `observed`, which none of
25737
- * these three reads raises — and then re-describes and can heal.
25972
+ * these four reads raises — and then re-describes and can heal.
25738
25973
  * Measured, not designed: the fabricated-account guard sits in exactly the
25739
25974
  * same place. Threading a sanitized SDK error as `cause` so a throttled
25740
25975
  * describe classifies as transient was considered and left out — a
@@ -25822,7 +26057,10 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25822
26057
  const literalList = Array.isArray(rawValues);
25823
26058
  const parts = (await allSettledKeepingFirstRejection(values.map(async (v) => {
25824
26059
  if (typeof v === "string") return {
25825
- ...v.includes("{{resolve:") ? await this.resolveDynamicReferencesWithLogTwin(v, v, context) : {
26060
+ ...v.includes("{{resolve:") ? await this.resolveDynamicReferencesWithLogTwin(v, this.logTwinOfProduct({
26061
+ result: v,
26062
+ twin: v
26063
+ }, context).twin, context) : {
25826
26064
  result: v,
25827
26065
  twin: v
25828
26066
  },
@@ -27144,8 +27382,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27144
27382
  * pass is masked too: the over-masking direction `logTwinOfProduct` accepts.
27145
27383
  */
27146
27384
  splitLogTwins(value, delimiter, pieces, context) {
27147
- const bag = this.logTwinBag(context);
27148
- const sourceTwin = this.isRecordedSecretForLog(value, context) ? "***" : bag ? LOG_TWINS_BY_PASS.get(bag)?.get(value) : void 0;
27385
+ const sourceTwin = this.isRecordedSecretForLog(value, context) ? "***" : this.registeredLogTwin(value, context);
27149
27386
  if (sourceTwin === void 0) return pieces;
27150
27387
  const twinPieces = sourceTwin.split(delimiter);
27151
27388
  const aligned = twinPieces.length === pieces.length && !"***".includes(delimiter);
@@ -27199,6 +27436,48 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27199
27436
  return context?.recordedSecretValues ?? context?.inheritedSecrets;
27200
27437
  }
27201
27438
  /**
27439
+ * The masked twin registered for `value`, looked up under the pass's OWN bag
27440
+ * and under the INHERITED one (issue
27441
+ * [#3114](https://github.com/go-to-k/cdkd/issues/3114)). A nested-stack
27442
+ * child receives, as `inheritedSecrets`, the very object its parent resolved
27443
+ * the `AWS::CloudFormation::Stack` resource with: the deploy engine binds the
27444
+ * resource context's `recordedSecretValues` as the resource's secrets, and
27445
+ * `NestedStackProvider` hands that binding to the child. So a parameter value
27446
+ * the parent built around a short secret (`port:q7` -> `port:***`) is
27447
+ * registered under the child's inherited bag, while the child registers its
27448
+ * own writes under its own bag. Two DIFFERENT registered twins mask the
27449
+ * whole string, the rule `rememberLogTwin` applies within one bag. Lookups
27450
+ * only: `rememberLogTwin` still registers under `logTwinBag`, the context's
27451
+ * own `recordedSecretValues` whenever it carries one (every context
27452
+ * `buildResolverContext` returns does; the log-only context
27453
+ * `resolveParameters` builds carries none, and nothing on that path
27454
+ * registers), so such a child never writes into its parent's registry. A
27455
+ * context with ONLY an inherited bag registers into that bag, as it did
27456
+ * before this lookup existed.
27457
+ *
27458
+ * ONE LEVEL ONLY. The bag a child hands its own nested stack is the child's
27459
+ * OWN resource bag, so no twin registered ABOVE the child is reachable from a
27460
+ * grandchild. Where the middle stack passes the value THROUGH
27461
+ * (`{ Ref: Param }` straight into the grandchild's `Parameters`) nothing ever
27462
+ * registers a twin in that bag, so the grandchild prints the value whether or
27463
+ * not the bag holds pairs. Where the middle RE-WRAPS it (an `Fn::Join` around
27464
+ * the `Ref`) the middle registers its own twin, and the hand-off still drops
27465
+ * a bag holding no pairs (it passes `inheritedSecrets` only when `size` is
27466
+ * nonzero). Both are issue #3156.
27467
+ *
27468
+ * The registry is keyed by VALUE, and `splitLogTwins`' unaligned arm
27469
+ * registers every piece, public ones included. So a child `Fn::Base64` over a
27470
+ * string exactly equal to such a piece of its parent's persists `***` for a
27471
+ * non-secret property, the over-masking hazard issue #3119 already accepts
27472
+ * within one pass, now reachable across the parent / child boundary.
27473
+ */
27474
+ registeredLogTwin(value, context) {
27475
+ const own = context?.recordedSecretValues ? LOG_TWINS_BY_PASS.get(context.recordedSecretValues)?.get(value) : void 0;
27476
+ const inherited = context?.inheritedSecrets ? LOG_TWINS_BY_PASS.get(context.inheritedSecrets)?.get(value) : void 0;
27477
+ if (own === void 0) return inherited;
27478
+ return inherited === void 0 || inherited === own ? own : "***";
27479
+ }
27480
+ /**
27202
27481
  * The log twin of a RESOLUTION PRODUCT — an intrinsic part, variable or
27203
27482
  * placeholder — for issue #3100. Masked whole when its value is a recorded
27204
27483
  * secret. Otherwise a part that carries no mask of its own takes the masked
@@ -27226,8 +27505,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27226
27505
  result: part.result,
27227
27506
  twin: "***"
27228
27507
  };
27229
- const bag = this.logTwinBag(context);
27230
- const registered = bag ? LOG_TWINS_BY_PASS.get(bag)?.get(part.result) : void 0;
27508
+ const registered = this.registeredLogTwin(part.result, context);
27231
27509
  if (registered === void 0 || registered === part.twin) return part;
27232
27510
  return {
27233
27511
  result: part.result,
@@ -27241,7 +27519,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
27241
27519
  * two DIFFERENT masked twins for one string register `***` for the whole
27242
27520
  * string, since their spans cannot be merged. Scoped to the pass's bag
27243
27521
  * through a `WeakMap`, like the cross-stack associations, so it dies with
27244
- * the bag and another pass's strings cannot be reached.
27522
+ * the bag. An unrelated pass cannot reach these strings; a nested child
27523
+ * whose `inheritedSecrets` is this bag reads them (`registeredLogTwin`).
27245
27524
  */
27246
27525
  rememberLogTwin(context, result, twin) {
27247
27526
  if (twin === result) return;
@@ -29016,7 +29295,7 @@ var CloudControlProvider = class {
29016
29295
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
29017
29296
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
29018
29297
  this.logger.debug(`Delegating protected AutoScalingGroup ${logicalId} delete to the SDK ASGProvider (Cloud Control cannot force-delete a protected ASG)`);
29019
- const { ASGProvider } = await import("./asg-provider-NffO_bpL.js").then((n) => n.n);
29298
+ const { ASGProvider } = await import("./asg-provider-CsdVn4cg.js").then((n) => n.n);
29020
29299
  const asgProvider = new ASGProvider();
29021
29300
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
29022
29301
  }
@@ -34214,20 +34493,22 @@ const WARM_THROUGHPUT_MEMBERS = ["ReadUnitsPerSecond", "WriteUnitsPerSecond"];
34214
34493
  *
34215
34494
  * The accepted STRING set is `Number()`'s, which is WIDER than a decimal
34216
34495
  * integer: `'0x1e'`, `'0o36'`, `'1e3'`, `'30.5'` and `' 30 '` all coerce.
34217
- * CloudFormation accepts only the LAST of those for an `Integer`-typed
34218
- * property — measured on `AWS::Logs::LogGroup.RetentionInDays`, issue
34219
- * [#2698](https://github.com/go-to-k/cdkd/issues/2698) — and
34220
- * {@link toCfnInteger} is the reader that matches that measurement. It matters
34221
- * at the CALLERS that FORWARD the result to AWS rather than merely compare it:
34222
- * the log-group provider switched; the DynamoDB capacity / throughput
34223
- * forwarders (`coerceWarmThroughput` below, and the `GlobalTable` provider's
34224
- * `ProvisionedThroughput` / `OnDemandThroughput` readers) still read through
34225
- * THIS helper, because the measurement covers one resource handler and
34226
- * DynamoDB's has not been A/B'd — issue
34227
- * [#3135](https://github.com/go-to-k/cdkd/issues/3135) holds that pass. A
34228
- * GUARD caller (`stateful-types.ts`'s `has-retention`, the DynamoDB
34229
- * already-matches / decrease tests) is safe on the wider set: over-acceptance
34230
- * there can only produce MORE refusals or skip fewer calls.
34496
+ * CloudFormation accepts NONE of those on a DynamoDB capacity and only the
34497
+ * LAST on `AWS::Logs::LogGroup.RetentionInDays` (both measured — the tables
34498
+ * are on {@link toCfnInteger}), so this helper is for the GUARD callers only:
34499
+ * `stateful-types.ts`'s `has-retention`, the DynamoDB already-matches /
34500
+ * decrease / zero-capacity tests, the live-readback compares. A guard is safe
34501
+ * on the wider set — over-acceptance there can only produce MORE refusals or
34502
+ * skip fewer calls. A caller that FORWARDS the number to AWS reads through the
34503
+ * grammar CloudFormation measured for that handler instead:
34504
+ * `logs-loggroup-provider.ts` through {@link toCfnInteger} (issue
34505
+ * [#2698](https://github.com/go-to-k/cdkd/issues/2698)), every DynamoDB
34506
+ * capacity / throughput forwarder (`coerceWarmThroughput` below, the
34507
+ * `GlobalTable` provider's `ProvisionedThroughput` / `OnDemandThroughput`
34508
+ * readers AND the diagnostic / mirror predicates paired with them) through
34509
+ * `config-shape.ts`'s `coerceCfnInteger` (issue
34510
+ * [#3135](https://github.com/go-to-k/cdkd/issues/3135)). A forwarder left on
34511
+ * THIS helper sends `Number('0x9')` for a template CloudFormation refuses.
34231
34512
  *
34232
34513
  * EXPORTED, and not because warm throughput needs it exported. This exact rule
34233
34514
  * was hand-written three times — here, as `dynamodb-table-provider.ts`'s
@@ -34289,6 +34570,37 @@ function toFiniteNumber(value) {
34289
34570
  * behaviour varies on padding, so each helper carries its own measurement and
34290
34571
  * a caller picks by the property it forwards.
34291
34572
  *
34573
+ * The DynamoDB measurement, read beside the Logs one so the two grammars sit
34574
+ * side by side (`AWS::DynamoDB::Table.ProvisionedThroughput.ReadCapacityUnits`
34575
+ * plus one `WriteCapacityUnits` row, us-east-1, 2026-09-14, issue
34576
+ * [#3135](https://github.com/go-to-k/cdkd/issues/3135); same protocol, live
34577
+ * value read back with `dynamodb describe-table`). "properties validation" is
34578
+ * CloudFormation's own template-property check, which fires BEFORE the
34579
+ * handler; "handler" is the DynamoDB resource handler's model validation:
34580
+ *
34581
+ * ```
34582
+ * template spelling CloudFormation live RCU after
34583
+ * 5 (JSON number) accepted 5
34584
+ * "6" accepted 6
34585
+ * "+8" accepted 8
34586
+ * "010" accepted (decimal, not octal) 10
34587
+ * " 7 " (padded) REJECTED (properties validation) unchanged
34588
+ * "7 " (trailing space) REJECTED (properties validation) unchanged
34589
+ * "" (empty string) REJECTED (properties validation) unchanged
34590
+ * "0x9" (RCU or WCU) REJECTED (handler) unchanged
34591
+ * "1e1" REJECTED (Validation failed) unchanged
34592
+ * "6.0" REJECTED (same) unchanged
34593
+ * "6.5" REJECTED (same) unchanged
34594
+ * ```
34595
+ *
34596
+ * The two agree on the digits (`/^[+-]?\d+$/`, hex / exponent / decimal-point
34597
+ * rejected by both) and DISAGREE on padding: the Logs handler trims, DynamoDB
34598
+ * rejects `" 7 "` and `"7 "` before any handler runs. So the DynamoDB
34599
+ * forwarders read through {@link coerceCfnInteger} itself — the no-trim
34600
+ * grammar — and NOT through this helper, whose trim is licensed by the Logs
34601
+ * measurement alone. `""` is not portable as "absent" either: on a REQUIRED
34602
+ * Integer it is rejected outright, where `RetentionInDays` reads it as absent.
34603
+ *
34292
34604
  * What this helper does NOT decide: the empty / whitespace-only strings above
34293
34605
  * are CloudFormation's spelling of "property absent", which is a statement
34294
34606
  * about the PROPERTY, not about the number — they answer `undefined` here, and
@@ -34322,6 +34634,16 @@ function toCfnInteger(value) {
34322
34634
  * Pure: takes the raw bag, returns numbers, logs nothing. Each caller owns its
34323
34635
  * own message, so the wording stays consistent across that provider's send
34324
34636
  * sites without forcing one wording across both providers.
34637
+ *
34638
+ * A member is read through {@link coerceCfnInteger} — CloudFormation's
34639
+ * measured DynamoDB Integer grammar (issue #3135, table on
34640
+ * {@link toCfnInteger}) — because this block is FORWARDED. A spelling
34641
+ * CloudFormation rejects (`" 7 "`, `"0x9"`, `"1e1"`, `"6.5"`) is therefore a
34642
+ * DROPPED member, named in `droppedMembers` exactly like an unresolved
34643
+ * intrinsic: the caller's diagnostic reports it and the live AWS value is left
34644
+ * alone. It is never read as ABSENT (the `=== undefined` test above runs on
34645
+ * the RAW value), and it is never forwarded as `Number()`'s reading, which
34646
+ * sent `7` for a template CloudFormation refuses.
34325
34647
  */
34326
34648
  function coerceWarmThroughput(raw) {
34327
34649
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { droppedMembers: [] };
@@ -34330,7 +34652,7 @@ function coerceWarmThroughput(raw) {
34330
34652
  const droppedMembers = [];
34331
34653
  for (const member of WARM_THROUGHPUT_MEMBERS) {
34332
34654
  if (bag[member] === void 0) continue;
34333
- const coerced = toFiniteNumber(bag[member]);
34655
+ const coerced = coerceCfnInteger(bag[member]);
34334
34656
  if (coerced === void 0) {
34335
34657
  droppedMembers.push(member);
34336
34658
  continue;
@@ -41672,5 +41994,5 @@ var DeployEngine = class DeployEngine {
41672
41994
  };
41673
41995
 
41674
41996
  //#endregion
41675
- export { secretSafeKeyDisplay as $, parseBootstrapMarker as $n, ConfigError as $r, DiffCalculator as $t, planOrphanAdoption as A, formatLockExpiry as An, resolveUseCdkBootstrapAssets as Ar, WAFv2WebACLProvider as At, formatResourceLine as B, AssetPublisher as Bn, PARTITION_TABLE as Br, replayWarn as Bt, createPreDeleteFinalSnapshot as C, isThrottlingError as Ci, scrubResourceRecord as Cn, getLegacyStateBucketName as Cr, cfnRefValueFromPhysicalId as Ct, makeCanonicalizePropertiesFn as D, retryClassificationText as Di, buildForceUnlockCommand as Dn, resolveSkipPrefix as Dr, parameterTypeMayLoseSecretIdentity as Dt, unsupportedFinalSnapshotError as E, markRedactedCause as Ei, rebuildClientForBucketRegion as En, resolveCaptureObservedState as Er, isUnboundTemplateParameter as Et, WARM_THROUGHPUT_MEMBERS as F, importableOutputKeys as Fn, MIGRATE_TMP_PREFIX as Fr, coerceCfnInteger as Ft, red as G, loadPublishableAssetManifest as Gn, clearBucketRegionCache as Gr, producerRegionsFromState as Gt, cyan as H, WorkGraph as Hn, derivePartitionAndUrlSuffix as Hr, requireConfigObject as Ht, coerceWarmThroughput as I, importableOutputs as In, findLargeInlineResources as Ir, configBooleanRefusal as It, collectPublishedOutputNames as J, BOOTSTRAP_MARKER_PREFIX as Jn, getAwsClients as Jr, s3BucketDomainName as Jt, yellow as K, rewriteTemplateAssetReferences as Kn, resolveBucketRegion as Kr, withSharedDrainBudget as Kt, isWarmThroughputDecrease as L, orphansAfterRollback as Ln, uploadCfnTemplate as Lr, configIntegerRefusal as Lt, isStatefulRecreateTargetForReplace as M, CUSTOM_RESOURCE_RESPONSE_PREFIX as Mn, warnDeprecatedNoPrefixCliFlag as Mr, resolveExplicitPhysicalId as Mt, isStatefulRecreateTargetSync as N, DEFAULT_STATE_PREFIX as Nn, CFN_TEMPLATE_BODY_LIMIT as Nr, assertRegionMatch as Nt, extractDeploymentEventError as O, __exportAll as Oi, buildLockContentionMessage as On, resolveStateBucketWithDefault as Or, refStateLookupFromResource as Ot, renderStatefulReason as P, exportNamesCarriedFrom as Pn, CFN_TEMPLATE_URL_LIMIT as Pr, coerceCfnBoolean as Pt, secretBearingStateKeyWarning as Q, isCrossRegionRedirect as Qn, CdkdError as Qr, applyRoleArnIfSet as Qt, toCfnInteger as R, orphansCarriedFrom as Rn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as Rr, configStringRefusal as Rt, ccRoutedFinalSnapshotError as S, isRetryableTransientError as Si, redactSecretsForState as Sn, getDefaultStateBucketName as Sr, carriesDynamicReference as St, refusesFinalSnapshot as T, markNonRetryable as Ti, S3StateBackend as Tn, resolveAutoAssetStorage as Tr, getAccountInfo as Tt, gray as U, buildAssetRedirectMap as Un, AssemblyReader as Ur, requireConfigString as Ut, bold as V, stringifyValue as Vn, canonicalizeRegion as Vr, requireConfigArray as Vt, green as W, createAssetRedirectResolver as Wn, processStackMessages as Wr, classifyReplaySecretRegion as Wt, isExportAliasCollision as X, ensureAssetStorage as Xn, setAwsClients as Xr, s3BucketRegionalDomainName as Xt, exportAliasCollisionScrubWarning as Y, assertAssetBucketRegion as Yn, resetAwsClients as Yr, s3BucketDualStackDomainName as Yt, secretBearing as Z, getBootstrapMarkerKey as Zn, AssetError as Zr, s3BucketWebsiteUrl as Zt, isSecretBearingReferenceString as _, formatError as _i, markSameGenerationBag as _n, stripControlChars as _r, deleteIndeterminateGuards as _t, DeploymentEventsStore as a, LocalInvokeBuildError as ai, DagBuilder as an, buildDockerImage as ar, wouldReturnToSdkProvider as at, PRE_DELETE_SNAPSHOT_TYPES as b, withErrorHandling as bi, recordMaskOnlyValue as bn, Synthesizer as br, isTerminationProtectionPropagationError as bt, replayFailedOperations as c, NestedStackChildDirectDestroyError as ci, STATE_SOURCED_CROSS_GENERATION_RULES as cn, describeDockerFailure as cr, maskerOrIdentity as ct, updatePartialReason as d, ResourceTimeoutError as di, carriesSecretMask as dn, getDockerCmd as dr, interruptWatchListenerCount as dt, CrossAccountSecretRefusalError as ei, INTRINSIC_KEYS as en, readBootstrapMarkerBody as er, getCurrentResourceSecrets as et, withResourceDeadline as f, ResourceUpdateNotSupportedError as fi, createSecretMasker as fn, partitionSensitiveEnv as fr, isInterruptedWaitError as ft, bagHoldsSecretExpression as g, SynthesisError as gi, isSingleDynamicReferenceToken as gn, escapeRegExp$1 as gr, UNSPECIFIED_SKIP_REASON as gt, computeImplicitDeleteEdges as h, StateError as hi, identityKeyFor as hn, runDockerStreaming as hr, slowCcOperationTimeoutMs as ht, DeploymentEventsReader as i, IntrinsicResolutionRefusalError as ii, withRetry as in, describeAwsFailure as ir, ProviderRegistry as it, MULTI_REGION_RECREATE_BLOCKED_TYPES as j, shellQuote as jn, stateBucketExistenceConfirmed as jr, normalizeAwsTagsToCfn as jt, makeSiblingClaimReader as k, forceQuitRecoveryClause as kn, resolveStateBucketWithDefaultAndSource as kr, isSettledInstanceState as kt, replayRollback as l, PartialFailureError as li, STATE_SOURCED_READBACK_RULES as ln, dockerSpawnEnvWithSensitive as lr, beginCommandInterruptScope as lt, IMPLICIT_DELETE_DEPENDENCIES as m, StackTerminationProtectionError as mi, errorCauseChain as mn, runDockerForeground as mr, CloudControlProvider as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, DeployCancelledError as ni, findSilentDropProperties as nn, validateContainerRepoName as nr, collectInlinePolicyNamesManagedBySiblings as nt, planFailedOps as o, LocalStartServiceError as oi, TemplateParser as on, describeDockerCapturedOutput as or, createMaskedRetryLogger as ot, maskingRetryLogger as p, StackHasActiveImportsError as pi, dynamicReferenceTokens as pn, redactDockerArgvValues as pr, startInterruptWatch as pt, collectDeclaredOutputNames as q, AssetModeResolver as qn, AwsClients as qr, s3BucketArn as qt, DeployEngine as r, DynamicReferenceRegionAmbiguousError as ri, describeTypeWithThrottleRetry as rn, buildDenyExternalAccessPolicy as rr, clearOnUpdateRemoval as rt, planRollback as s, LockError as si, STATE_SOURCED_BASELINE_RULES as sn, describeDockerExecFailure as sr, maskDeep as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, DependencyError as ti, findActionableSilentDrops as tn, validateAssetBucketName as tr, IAMRoleProvider as tt, updatePartialMessage as u, ProvisioningError as ui, TEMPLATE_SOURCED_RULES as un, formatDockerLoginError as ur, endCommandInterruptScope as ut, bindingSkippedOutputs as v, isCdkdError as vi, maskSecretsInError as vn, AssetManifestLoader as vr, deleteSkipReason as vt, isFinalSnapshotError as w, isTransientServerError as wi, LockManager as wn, resolveApp as wr, coerceParameterTypedValue as wt, buildFinalSnapshotIdentifier as x, isMarkedNonRetryable as xi, recoverMaskedOutput as xn, synthesisStatusMessage as xr, IntrinsicFunctionResolver as xt, ATOMIC_FINAL_SNAPSHOT_TYPES as y, normalizeAwsError as yi, maskSecretsInText as yn, getDockerImageBySourceHash as yr, disableInstanceApiTermination as yt, toFiniteNumber as z, shouldRetainResource as zn, expectedOwnerParam as zr, readConfigString as zt };
41676
- //# sourceMappingURL=deploy-engine-DD7JE_Ic.js.map
41997
+ export { secretSafeKeyDisplay as $, isCrossRegionRedirect as $n, CdkdError as $r, applyRoleArnIfSet as $t, planOrphanAdoption as A, forceQuitRecoveryClause as An, resolveStateBucketWithDefaultAndSource as Ar, isSettledInstanceState as At, formatResourceLine as B, shouldRetainResource as Bn, expectedOwnerParam as Br, readConfigString as Bt, createPreDeleteFinalSnapshot as C, isRetryableTransientError as Ci, redactSecretsForState as Cn, getDefaultStateBucketName as Cr, cfnRefValueFromPhysicalId as Ct, makeCanonicalizePropertiesFn as D, markRedactedCause as Di, rebuildClientForBucketRegion as Dn, resolveCaptureObservedState as Dr, isUnboundTemplateParameter as Dt, unsupportedFinalSnapshotError as E, markNonRetryable as Ei, S3StateBackend as En, resolveAutoAssetStorage as Er, isImpossibleEmptyStoredAttribute as Et, WARM_THROUGHPUT_MEMBERS as F, exportNamesCarriedFrom as Fn, CFN_TEMPLATE_URL_LIMIT as Fr, coerceCfnBoolean as Ft, red as G, createAssetRedirectResolver as Gn, processStackMessages as Gr, classifyReplaySecretRegion as Gt, cyan as H, stringifyValue as Hn, canonicalizeRegion as Hr, requireConfigArray as Ht, coerceWarmThroughput as I, importableOutputKeys as In, MIGRATE_TMP_PREFIX as Ir, coerceCfnInteger as It, collectPublishedOutputNames as J, AssetModeResolver as Jn, AwsClients as Jr, s3BucketArn as Jt, yellow as K, loadPublishableAssetManifest as Kn, clearBucketRegionCache as Kr, producerRegionsFromState as Kt, isWarmThroughputDecrease as L, importableOutputs as Ln, findLargeInlineResources as Lr, configBooleanRefusal as Lt, isStatefulRecreateTargetForReplace as M, shellQuote as Mn, stateBucketExistenceConfirmed as Mr, normalizeAwsTagsToCfn as Mt, isStatefulRecreateTargetSync as N, CUSTOM_RESOURCE_RESPONSE_PREFIX as Nn, warnDeprecatedNoPrefixCliFlag as Nr, resolveExplicitPhysicalId as Nt, extractDeploymentEventError as O, retryClassificationText as Oi, buildForceUnlockCommand as On, resolveSkipPrefix as Or, parameterTypeMayLoseSecretIdentity as Ot, renderStatefulReason as P, DEFAULT_STATE_PREFIX as Pn, CFN_TEMPLATE_BODY_LIMIT as Pr, assertRegionMatch as Pt, secretBearingStateKeyWarning as Q, getBootstrapMarkerKey as Qn, AssetError as Qr, s3BucketWebsiteUrl as Qt, toCfnInteger as R, orphansAfterRollback as Rn, uploadCfnTemplate as Rr, configIntegerRefusal as Rt, ccRoutedFinalSnapshotError as S, isMarkedNonRetryable as Si, recoverMaskedOutput as Sn, synthesisStatusMessage as Sr, carriesDynamicReference as St, refusesFinalSnapshot as T, isTransientServerError as Ti, LockManager as Tn, resolveApp as Tr, getAccountInfo as Tt, gray as U, WorkGraph as Un, derivePartitionAndUrlSuffix as Ur, requireConfigObject as Ut, bold as V, AssetPublisher as Vn, PARTITION_TABLE as Vr, replayWarn as Vt, green as W, buildAssetRedirectMap as Wn, AssemblyReader as Wr, requireConfigString as Wt, isExportAliasCollision as X, assertAssetBucketRegion as Xn, resetAwsClients as Xr, s3BucketDualStackDomainName as Xt, exportAliasCollisionScrubWarning as Y, BOOTSTRAP_MARKER_PREFIX as Yn, getAwsClients as Yr, s3BucketDomainName as Yt, secretBearing as Z, ensureAssetStorage as Zn, setAwsClients as Zr, s3BucketRegionalDomainName as Zt, isSecretBearingReferenceString as _, SynthesisError as _i, isSingleDynamicReferenceToken as _n, escapeRegExp$1 as _r, deleteIndeterminateGuards as _t, DeploymentEventsStore as a, IntrinsicResolutionRefusalError as ai, withRetry as an, describeAwsFailure as ar, wouldReturnToSdkProvider as at, PRE_DELETE_SNAPSHOT_TYPES as b, normalizeAwsError as bi, maskSecretsInText as bn, getDockerImageBySourceHash as br, isTerminationProtectionPropagationError as bt, replayFailedOperations as c, LockError as ci, STATE_SOURCED_BASELINE_RULES as cn, describeDockerExecFailure as cr, maskerOrIdentity as ct, updatePartialReason as d, ProvisioningError as di, TEMPLATE_SOURCED_RULES as dn, formatDockerLoginError as dr, interruptWatchListenerCount as dt, ConfigError as ei, DiffCalculator as en, parseBootstrapMarker as er, getCurrentResourceSecrets as et, withResourceDeadline as f, ResourceTimeoutError as fi, carriesSecretMask as fn, getDockerCmd as fr, isInterruptedWaitError as ft, bagHoldsSecretExpression as g, StateError as gi, identityKeyFor as gn, runDockerStreaming as gr, UNSPECIFIED_SKIP_REASON as gt, computeImplicitDeleteEdges as h, StackTerminationProtectionError as hi, errorCauseChain as hn, runDockerForeground as hr, slowCcOperationTimeoutMs as ht, DeploymentEventsReader as i, DynamicReferenceRegionAmbiguousError as ii, describeTypeWithThrottleRetry as in, buildDenyExternalAccessPolicy as ir, ProviderRegistry as it, MULTI_REGION_RECREATE_BLOCKED_TYPES as j, formatLockExpiry as jn, resolveUseCdkBootstrapAssets as jr, WAFv2WebACLProvider as jt, makeSiblingClaimReader as k, __exportAll as ki, buildLockContentionMessage as kn, resolveStateBucketWithDefault as kr, refStateLookupFromResource as kt, replayRollback as l, NestedStackChildDirectDestroyError as li, STATE_SOURCED_CROSS_GENERATION_RULES as ln, describeDockerFailure as lr, beginCommandInterruptScope as lt, IMPLICIT_DELETE_DEPENDENCIES as m, StackHasActiveImportsError as mi, dynamicReferenceTokens as mn, redactDockerArgvValues as mr, CloudControlProvider as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, DependencyError as ni, findActionableSilentDrops as nn, validateAssetBucketName as nr, collectInlinePolicyNamesManagedBySiblings as nt, planFailedOps as o, LocalInvokeBuildError as oi, DagBuilder as on, buildDockerImage as or, createMaskedRetryLogger as ot, maskingRetryLogger as p, ResourceUpdateNotSupportedError as pi, createSecretMasker as pn, partitionSensitiveEnv as pr, startInterruptWatch as pt, collectDeclaredOutputNames as q, rewriteTemplateAssetReferences as qn, resolveBucketRegion as qr, withSharedDrainBudget as qt, DeployEngine as r, DeployCancelledError as ri, findSilentDropProperties as rn, validateContainerRepoName as rr, clearOnUpdateRemoval as rt, planRollback as s, LocalStartServiceError as si, TemplateParser as sn, describeDockerCapturedOutput as sr, maskDeep as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, CrossAccountSecretRefusalError as ti, INTRINSIC_KEYS as tn, readBootstrapMarkerBody as tr, IAMRoleProvider as tt, updatePartialMessage as u, PartialFailureError as ui, STATE_SOURCED_READBACK_RULES as un, dockerSpawnEnvWithSensitive as ur, endCommandInterruptScope as ut, bindingSkippedOutputs as v, formatError as vi, markSameGenerationBag as vn, stripControlChars as vr, deleteSkipReason as vt, isFinalSnapshotError as w, isThrottlingError as wi, scrubResourceRecord as wn, getLegacyStateBucketName as wr, coerceParameterTypedValue as wt, buildFinalSnapshotIdentifier as x, withErrorHandling as xi, recordMaskOnlyValue as xn, Synthesizer as xr, IntrinsicFunctionResolver as xt, ATOMIC_FINAL_SNAPSHOT_TYPES as y, isCdkdError as yi, maskSecretsInError as yn, AssetManifestLoader as yr, disableInstanceApiTermination as yt, toFiniteNumber as z, orphansCarriedFrom as zn, CUSTOM_RESOURCE_RESPONSE_OBJECT_DESCRIPTION as zr, configStringRefusal as zt };
41998
+ //# sourceMappingURL=deploy-engine-BU7n9_qY.js.map