@go-to-k/cdkd 0.290.4 → 0.290.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as isVersionOnlyInvocation, t as getCdkdVersion } from "./version-BUcQz4SU.js";
2
+ import { n as isVersionOnlyInvocation, t as getCdkdVersion } from "./version-BBIkp8ja.js";
3
3
 
4
4
  //#region src/cli/pipe-close-handler.ts
5
5
  /**
@@ -74,7 +74,7 @@ async function main() {
74
74
  console.log(getCdkdVersion());
75
75
  return;
76
76
  }
77
- const { buildProgram } = await import("./program-20PIJFu9.js");
77
+ const { buildProgram } = await import("./program-D30EPjOn.js");
78
78
  const program = buildProgram();
79
79
  const args = reorderArgs(process.argv);
80
80
  await program.parseAsync(args);
@@ -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-BBIkp8ja.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";
@@ -9164,9 +9164,60 @@ function safeJournalText(value) {
9164
9164
  return displaySafe(value, { asciiOnly: true }) || "<unrenderable>";
9165
9165
  }
9166
9166
  /**
9167
+ * The per-operation shape check (issue #3140). The journal is an unchecked
9168
+ * cast beyond `journalVersion` and `segments` being an array, and the
9169
+ * executor keys EVERY lookup on `op.logicalId` -- `stateResources[..]`,
9170
+ * `orphanLogicalIds.has(..)`, the events it records -- so a non-string id
9171
+ * planted in the bucket either coerces at each lookup (`123` finds the
9172
+ * record named `'123'`) or throws a raw `TypeError` at the first one (an
9173
+ * object whose `toString` is not callable, the issue #2947 shape). Refused
9174
+ * HERE, once, so no downstream reader has to remember; the executor keeps its
9175
+ * own `typeof` guard on the one pasted command as defense-in-depth, because
9176
+ * the deploy engine's in-process rollback reaches it without this parser.
9177
+ *
9178
+ * Only the fields a lookup or a route is keyed on: `logicalId` (non-empty
9179
+ * string), `resourceType` (string), `changeType` (string -- an unknown value
9180
+ * lands on the executor's UPDATE arm by design, pinned by a case there), and
9181
+ * `physicalId` when present (string). `previousState` and the rest stay as
9182
+ * they were; a bad value there fails the op it belongs to, not the parse.
9183
+ *
9184
+ * The refusal names the INDEX and the TYPE, never the value -- there is
9185
+ * nothing to sanitize, so nothing to get wrong.
9186
+ */
9187
+ function refuseMalformedOperation(shownStack, where, op) {
9188
+ const fail = (field, value, expected) => refuseMalformed(shownStack, `${where}.${field} must be ${expected} (got ${kind(value)}).`);
9189
+ if (typeof op !== "object" || op === null || Array.isArray(op)) refuseMalformed(shownStack, `${where} must be an object (got ${kind(op)}).`);
9190
+ const o = op;
9191
+ if (typeof o["logicalId"] !== "string" || o["logicalId"].length === 0) fail("logicalId", o["logicalId"], "a non-empty string");
9192
+ if (typeof o["resourceType"] !== "string") fail("resourceType", o["resourceType"], "a string");
9193
+ if (typeof o["changeType"] !== "string") fail("changeType", o["changeType"], "a string");
9194
+ if (o["physicalId"] !== void 0 && typeof o["physicalId"] !== "string") fail("physicalId", o["physicalId"], "a string when present");
9195
+ }
9196
+ /** The TYPE of a JSON-derived value, for a refusal that must not echo the value. */
9197
+ function kind(v) {
9198
+ return v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
9199
+ }
9200
+ /**
9201
+ * The one spelling of a malformed-journal refusal. `detail` is built from
9202
+ * indices, field names and `kind()` words only -- never a journal value -- and
9203
+ * every refusal names the way out, as `UnknownRollbackJournalVersionError`
9204
+ * names `Upgrade cdkd`: the journal is a sibling of `state.json`, so removing
9205
+ * that one object discards it (`cdkd destroy` sweeps it as well), and no
9206
+ * other stack is affected because `cdkd rollback` never parses another
9207
+ * stack's journal. The same text reaches the deploy engine's `Failed to write
9208
+ * rollback journal` warning, since appending a segment parses the existing
9209
+ * journal first.
9210
+ */
9211
+ function refuseMalformed(shownStack, detail) {
9212
+ 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.`);
9213
+ }
9214
+ /**
9167
9215
  * Parse + validate a journal body. Throws
9168
9216
  * {@link UnknownRollbackJournalVersionError} on a newer version, and a plain
9169
- * Error on a structurally-invalid body.
9217
+ * Error on a structurally-invalid body -- including, since issue #3140, a
9218
+ * segment that is not an object, an `operations` that is not an array, and an
9219
+ * operation whose `logicalId` / `resourceType` / `changeType` / `physicalId`
9220
+ * is not the string the executor keys on (see `refuseMalformedOperation`).
9170
9221
  */
9171
9222
  function parseRollbackJournal(bodyString, stackName) {
9172
9223
  const shownStack = safeJournalText(stackName);
@@ -9186,6 +9237,16 @@ function parseRollbackJournal(bodyString, stackName) {
9186
9237
  }
9187
9238
  if (j.journalVersion > 1) throw new UnknownRollbackJournalVersionError(j.journalVersion, stackName);
9188
9239
  if (!Array.isArray(j.segments)) throw new Error(`Rollback journal for '${shownStack}' is missing a 'segments' array.`);
9240
+ j.segments.forEach((segment, s) => {
9241
+ if (typeof segment !== "object" || segment === null || Array.isArray(segment)) refuseMalformed(shownStack, `segments[${s}] must be an object (got ${kind(segment)}).`);
9242
+ const seg = segment;
9243
+ if (!Array.isArray(seg["operations"])) refuseMalformed(shownStack, `segments[${s}].operations must be an array (got ${kind(seg["operations"])}).`);
9244
+ seg["operations"].forEach((op, i) => refuseMalformedOperation(shownStack, `segments[${s}].operations[${i}]`, op));
9245
+ if (seg["failedOperations"] !== void 0) {
9246
+ if (!Array.isArray(seg["failedOperations"])) refuseMalformed(shownStack, `segments[${s}].failedOperations must be an array when present (got ${kind(seg["failedOperations"])}).`);
9247
+ seg["failedOperations"].forEach((op, i) => refuseMalformedOperation(shownStack, `segments[${s}].failedOperations[${i}]`, op));
9248
+ }
9249
+ });
9189
9250
  return {
9190
9251
  journalVersion: j.journalVersion,
9191
9252
  stackName: j.stackName ?? stackName,
@@ -13148,7 +13209,11 @@ const UNKNOWN_PART_PLACEHOLDER = "\0";
13148
13209
  * longer equals the leaf's text around the middle, so the arm refuses) -- and,
13149
13210
  * through the frame arm's rewrite, for the nested-stack parameter recorder's
13150
13211
  * `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
13212
+ * the position pass rewrote it -- which, on a value the recorder's gate (ii)
13213
+ * finds the scan silent on, only the frame arm can do (the value scan, the
13214
+ * skeleton arm and the cross-stack arm each rewrite an object-sourced leaf
13215
+ * whose WHOLE value is a recorded plaintext, and (ii) is what refuses that
13216
+ * value). The recorder's own rendering of such a part does not
13152
13217
  * decide the case: a part outside the token leaves its one span and service
13153
13218
  * prefix the same either way.
13154
13219
  *
@@ -13422,8 +13487,15 @@ function positionByCrossStackSource(bag, source, secrets) {
13422
13487
  * the source leaf is an `Fn::Join` / `Fn::Sub` there is none, so the leaf fell
13423
13488
  * to the value scan — and the value map is keyed by the resolved PLAINTEXT, so
13424
13489
  * 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`.
13490
+ * DOMINANT CDK shape rather than an edge case: a secret reached through an L2
13491
+ * token usually renders the secret ARN as a `Ref`, hence an `Fn::Join`. The
13492
+ * exceptions render a plain `{{resolve:...}}` string, which the literal arm
13493
+ * already positions: a secret imported by a LITERAL ARN
13494
+ * (`Secret.fromSecretCompleteArn` / `fromSecretPartialArn`, whose
13495
+ * `secretValue` and `secretValueFromJson` both inline it),
13496
+ * `SecretValue.secretsManager('<literal arn>')` and
13497
+ * `SecretValue.ssmSecure(...)` (synth-probed on this repo's aws-cdk-lib,
13498
+ * issue #3143; `new Secret(...)` and `fromSecretNameV2` are the join).
13427
13499
  *
13428
13500
  * Three conditions must ALL hold before an expression is persisted, and each
13429
13501
  * removes a different way of being wrong:
@@ -23430,20 +23502,22 @@ const recordedSecretExpressions = {
23430
23502
  */
23431
23503
  const cachedEc2InstanceAttributes = Object.create(null);
23432
23504
  /**
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
23505
+ * The three sibling caches of {@link cachedEc2InstanceAttributes} (issues
23506
+ * #3096 / #3097), each holding a value the resolver read LIVE because the
23507
+ * state record omitted it — `definedAttributes` drops a member the provider
23508
+ * could not read back (issue #3077), so the `Fn::GetAtt` falls out of the
23509
+ * flat lookup and into `constructAttribute`'s per-type arm. Keyed by physical
23510
+ * id: the read is region-pinned (`clientsForRegion(this.explicitRegion)`),
23511
+ * so the key names one resource in that region, and the value — a default
23512
+ * security group id, a distribution hostname, a security group's VPC —
23513
+ * never changes; like the instance cache, a refusal caches nothing. Same
23514
+ * process lifetime, cleared together with it by {@link resetAccountInfoCache}.
23515
+ * The RDS `DBProxy` / `DBProxyEndpoint` `VpcId` arms have no cache because
23516
+ * they have no live read: `AwsClients` exposes no RDS client and this file
23517
+ * imports none, so those two arms refuse outright (see
23518
+ * `refuseUnservedAttribute`'s callers).
23519
+ *
23520
+ * All four are NULL-PROTOTYPE objects (#3096 delta review, measured): the
23447
23521
  * key is a state-record physical id, and on a plain `{}` a record holding
23448
23522
  * `constructor` / `__proto__` / `hasOwnProperty` read a FUNCTION out of
23449
23523
  * `Object.prototype` as the cached value — served with zero AWS calls, ahead
@@ -23452,6 +23526,7 @@ const cachedEc2InstanceAttributes = Object.create(null);
23452
23526
  */
23453
23527
  const cachedVpcDefaultSecurityGroups = Object.create(null);
23454
23528
  const cachedCloudFrontDomainNames = Object.create(null);
23529
+ const cachedSecurityGroupVpcIds = Object.create(null);
23455
23530
  /**
23456
23531
  * The region this call answers for: the caller's override, else the ambient one.
23457
23532
  *
@@ -23900,6 +23975,29 @@ async function resolveAccountIdentity() {
23900
23975
  }
23901
23976
  }
23902
23977
  /**
23978
+ * Is a STORED `''` for this attribute a value the resource can never have —
23979
+ * so the flat lookup must read it as ABSENT and let the live arm run?
23980
+ *
23981
+ * Exactly one attribute qualifies today (issue #3097 review): an
23982
+ * `AWS::EC2::SecurityGroup`'s `VpcId`. Every security group lives in a VPC
23983
+ * (EC2-Classic retired 2022-08-15), so `''` can only be the pre-#3097
23984
+ * provider's copy of a template that declared no `VpcId` — and that record is
23985
+ * rewritten only by an `update()`, which a no-change deploy never issues, so
23986
+ * without this carve-out the stored `''` shadows the live arm for the life of
23987
+ * the record. The record itself stays `''` until the next update; only the
23988
+ * RESOLUTION changes.
23989
+ *
23990
+ * Deliberately NOT a general "`''` means absent" rule: #3077 records a settled
23991
+ * EC2 instance's missing public address as the KNOWN empty `''`
23992
+ * (CloudFormation's own answer), and that one must keep being served from
23993
+ * state rather than sent back to `DescribeInstances` on every resolution.
23994
+ * A new entry here needs the same argument — that `''` is IMPOSSIBLE for the
23995
+ * attribute, not merely unlikely.
23996
+ */
23997
+ function isImpossibleEmptyStoredAttribute(resourceType, attributeName, storedValue) {
23998
+ return storedValue === "" && resourceType === "AWS::EC2::SecurityGroup" && attributeName === "VpcId";
23999
+ }
24000
+ /**
23903
24001
  * Does a constructed `Fn::GetAtt` answer embed the placeholder account id?
23904
24002
  *
23905
24003
  * The guard in `constructGuardedAttribute` used to test
@@ -25133,7 +25231,8 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25133
25231
  const resource = Object.hasOwn(context.resources, logicalId) ? context.resources[logicalId] : void 0;
25134
25232
  if (!resource) throw markNonRetryable(/* @__PURE__ */ new Error(`Resource ${logicalId} not found for Fn::GetAtt`));
25135
25233
  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;
25234
+ const storedValue = Object.hasOwn(resource.attributes, attributeName) ? resource.attributes[attributeName] : void 0;
25235
+ const flatValue = isImpossibleEmptyStoredAttribute(resource.resourceType, attributeName, storedValue) ? void 0 : storedValue;
25137
25236
  if (flatValue !== void 0) {
25138
25237
  this.rejectPlaceholderArnAttribute(resource, attributeName, flatValue, logicalId, context);
25139
25238
  if (resource.resourceType === "AWS::Route53::HostedZone" && attributeName === "NameServers" && typeof flatValue === "string") {
@@ -25578,7 +25677,38 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25578
25677
  }
25579
25678
  if (resourceType === "AWS::EC2::SecurityGroup") switch (attributeName) {
25580
25679
  case "GroupId": return physicalId;
25581
- case "VpcId": return;
25680
+ case "VpcId": {
25681
+ 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.`));
25682
+ const cachedVpcId = cachedSecurityGroupVpcIds[physicalId];
25683
+ if (cachedVpcId !== void 0) return cachedVpcId;
25684
+ let vpcId;
25685
+ try {
25686
+ vpcId = (await this.clientsForRegion(this.explicitRegion).ec2.send(new DescribeSecurityGroupsCommand({ GroupIds: [physicalId] }))).SecurityGroups?.[0]?.VpcId;
25687
+ } catch (err) {
25688
+ this.refuseUnservedAttribute({
25689
+ logicalId,
25690
+ attributeName,
25691
+ resourceType,
25692
+ physicalId,
25693
+ context,
25694
+ observed: this.describeFailureObserved("DescribeSecurityGroups", err, context),
25695
+ remedy: "Fix the read (the ec2:DescribeSecurityGroups permission, or the region) and deploy again."
25696
+ });
25697
+ }
25698
+ if (vpcId) {
25699
+ cachedSecurityGroupVpcIds[physicalId] = vpcId;
25700
+ return vpcId;
25701
+ }
25702
+ this.refuseUnservedAttribute({
25703
+ logicalId,
25704
+ attributeName,
25705
+ resourceType,
25706
+ physicalId,
25707
+ context,
25708
+ observed: "DescribeSecurityGroups reports no VpcId for the group",
25709
+ remedy: "Check the security group in the console, or reference the attribute from a later deploy."
25710
+ });
25711
+ }
25582
25712
  default: return this.guardedPhysicalIdFallback(logicalId, attributeName, resourceType, physicalId, context);
25583
25713
  }
25584
25714
  if (resourceType === "AWS::EC2::Subnet") switch (attributeName) {
@@ -25716,9 +25846,12 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25716
25846
  * belongs, a `vpc-...` in a security-group slot, a distribution id where a
25717
25847
  * hostname belongs, a proxy name in a `VpcId` position. Loud only where AWS
25718
25848
  * 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.
25849
+ * silently. The security group `VpcId` arm (issue #3097) joined with the
25850
+ * opposite pre-fix defect — it answered `undefined`, which `Fn::Join`
25851
+ * renders `'undefined'` — and takes the same refusal. The physical id can
25852
+ * never be the value for these attributes, so the honest answer is a
25853
+ * refusal naming the resource, the attribute, what was observed and the
25854
+ * remedy.
25722
25855
  *
25723
25856
  * DELIBERATELY NOT `markNonRetryable`, unlike every other refusal in this
25724
25857
  * file but the fabricated-account guard: the verdict is TIME-DEPENDENT. A
@@ -25734,7 +25867,7 @@ var IntrinsicFunctionResolver = class IntrinsicFunctionResolver {
25734
25867
  * interpolated hole happens to carry a pattern — the `logicalId` (the #1838
25735
25868
  * hazard: `DependencyViolation` and two other bare words), or the error
25736
25869
  * CLASS name `describeFailureObserved` puts in `observed`, which none of
25737
- * these three reads raises — and then re-describes and can heal.
25870
+ * these four reads raises — and then re-describes and can heal.
25738
25871
  * Measured, not designed: the fabricated-account guard sits in exactly the
25739
25872
  * same place. Threading a sanitized SDK error as `cause` so a throttled
25740
25873
  * describe classifies as transient was considered and left out — a
@@ -29016,7 +29149,7 @@ var CloudControlProvider = class {
29016
29149
  const indeterminateGuard = await this.confirmDeleteTargetIdentity(logicalId, resourceType, physicalId, context);
29017
29150
  if (context?.removeProtection === true && resourceType === "AWS::AutoScaling::AutoScalingGroup") {
29018
29151
  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);
29152
+ const { ASGProvider } = await import("./asg-provider-DU7mN-cS.js").then((n) => n.n);
29020
29153
  const asgProvider = new ASGProvider();
29021
29154
  return withIndeterminateGuard(await asgProvider.delete(logicalId, physicalId, resourceType, _properties, context), indeterminateGuard);
29022
29155
  }
@@ -34214,20 +34347,22 @@ const WARM_THROUGHPUT_MEMBERS = ["ReadUnitsPerSecond", "WriteUnitsPerSecond"];
34214
34347
  *
34215
34348
  * The accepted STRING set is `Number()`'s, which is WIDER than a decimal
34216
34349
  * 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.
34350
+ * CloudFormation accepts NONE of those on a DynamoDB capacity and only the
34351
+ * LAST on `AWS::Logs::LogGroup.RetentionInDays` (both measured — the tables
34352
+ * are on {@link toCfnInteger}), so this helper is for the GUARD callers only:
34353
+ * `stateful-types.ts`'s `has-retention`, the DynamoDB already-matches /
34354
+ * decrease / zero-capacity tests, the live-readback compares. A guard is safe
34355
+ * on the wider set — over-acceptance there can only produce MORE refusals or
34356
+ * skip fewer calls. A caller that FORWARDS the number to AWS reads through the
34357
+ * grammar CloudFormation measured for that handler instead:
34358
+ * `logs-loggroup-provider.ts` through {@link toCfnInteger} (issue
34359
+ * [#2698](https://github.com/go-to-k/cdkd/issues/2698)), every DynamoDB
34360
+ * capacity / throughput forwarder (`coerceWarmThroughput` below, the
34361
+ * `GlobalTable` provider's `ProvisionedThroughput` / `OnDemandThroughput`
34362
+ * readers AND the diagnostic / mirror predicates paired with them) through
34363
+ * `config-shape.ts`'s `coerceCfnInteger` (issue
34364
+ * [#3135](https://github.com/go-to-k/cdkd/issues/3135)). A forwarder left on
34365
+ * THIS helper sends `Number('0x9')` for a template CloudFormation refuses.
34231
34366
  *
34232
34367
  * EXPORTED, and not because warm throughput needs it exported. This exact rule
34233
34368
  * was hand-written three times — here, as `dynamodb-table-provider.ts`'s
@@ -34289,6 +34424,37 @@ function toFiniteNumber(value) {
34289
34424
  * behaviour varies on padding, so each helper carries its own measurement and
34290
34425
  * a caller picks by the property it forwards.
34291
34426
  *
34427
+ * The DynamoDB measurement, read beside the Logs one so the two grammars sit
34428
+ * side by side (`AWS::DynamoDB::Table.ProvisionedThroughput.ReadCapacityUnits`
34429
+ * plus one `WriteCapacityUnits` row, us-east-1, 2026-09-14, issue
34430
+ * [#3135](https://github.com/go-to-k/cdkd/issues/3135); same protocol, live
34431
+ * value read back with `dynamodb describe-table`). "properties validation" is
34432
+ * CloudFormation's own template-property check, which fires BEFORE the
34433
+ * handler; "handler" is the DynamoDB resource handler's model validation:
34434
+ *
34435
+ * ```
34436
+ * template spelling CloudFormation live RCU after
34437
+ * 5 (JSON number) accepted 5
34438
+ * "6" accepted 6
34439
+ * "+8" accepted 8
34440
+ * "010" accepted (decimal, not octal) 10
34441
+ * " 7 " (padded) REJECTED (properties validation) unchanged
34442
+ * "7 " (trailing space) REJECTED (properties validation) unchanged
34443
+ * "" (empty string) REJECTED (properties validation) unchanged
34444
+ * "0x9" (RCU or WCU) REJECTED (handler) unchanged
34445
+ * "1e1" REJECTED (Validation failed) unchanged
34446
+ * "6.0" REJECTED (same) unchanged
34447
+ * "6.5" REJECTED (same) unchanged
34448
+ * ```
34449
+ *
34450
+ * The two agree on the digits (`/^[+-]?\d+$/`, hex / exponent / decimal-point
34451
+ * rejected by both) and DISAGREE on padding: the Logs handler trims, DynamoDB
34452
+ * rejects `" 7 "` and `"7 "` before any handler runs. So the DynamoDB
34453
+ * forwarders read through {@link coerceCfnInteger} itself — the no-trim
34454
+ * grammar — and NOT through this helper, whose trim is licensed by the Logs
34455
+ * measurement alone. `""` is not portable as "absent" either: on a REQUIRED
34456
+ * Integer it is rejected outright, where `RetentionInDays` reads it as absent.
34457
+ *
34292
34458
  * What this helper does NOT decide: the empty / whitespace-only strings above
34293
34459
  * are CloudFormation's spelling of "property absent", which is a statement
34294
34460
  * about the PROPERTY, not about the number — they answer `undefined` here, and
@@ -34322,6 +34488,16 @@ function toCfnInteger(value) {
34322
34488
  * Pure: takes the raw bag, returns numbers, logs nothing. Each caller owns its
34323
34489
  * own message, so the wording stays consistent across that provider's send
34324
34490
  * sites without forcing one wording across both providers.
34491
+ *
34492
+ * A member is read through {@link coerceCfnInteger} — CloudFormation's
34493
+ * measured DynamoDB Integer grammar (issue #3135, table on
34494
+ * {@link toCfnInteger}) — because this block is FORWARDED. A spelling
34495
+ * CloudFormation rejects (`" 7 "`, `"0x9"`, `"1e1"`, `"6.5"`) is therefore a
34496
+ * DROPPED member, named in `droppedMembers` exactly like an unresolved
34497
+ * intrinsic: the caller's diagnostic reports it and the live AWS value is left
34498
+ * alone. It is never read as ABSENT (the `=== undefined` test above runs on
34499
+ * the RAW value), and it is never forwarded as `Number()`'s reading, which
34500
+ * sent `7` for a template CloudFormation refuses.
34325
34501
  */
34326
34502
  function coerceWarmThroughput(raw) {
34327
34503
  if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { droppedMembers: [] };
@@ -34330,7 +34506,7 @@ function coerceWarmThroughput(raw) {
34330
34506
  const droppedMembers = [];
34331
34507
  for (const member of WARM_THROUGHPUT_MEMBERS) {
34332
34508
  if (bag[member] === void 0) continue;
34333
- const coerced = toFiniteNumber(bag[member]);
34509
+ const coerced = coerceCfnInteger(bag[member]);
34334
34510
  if (coerced === void 0) {
34335
34511
  droppedMembers.push(member);
34336
34512
  continue;
@@ -41672,5 +41848,5 @@ var DeployEngine = class DeployEngine {
41672
41848
  };
41673
41849
 
41674
41850
  //#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
41851
+ 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 };
41852
+ //# sourceMappingURL=deploy-engine-WFO9o9M0.js.map