+- ✅ **`AWS::DynamoDB::GlobalTable`: GSI throughput translated to the `CreateTable` SDK shape, so a PROVISIONED GlobalTable with a GSI stops failing outright (issue [#1387](https://github.com/go-to-k/cdkd/issues/1387))** — `src/provisioning/providers/dynamodb-globaltable-provider.ts`, a new unit-test file, and the `tests/integration/dynamodb-globaltable/` fixture. **The class:** the provider cast the CFn `GlobalSecondaryIndexes` blob RAW to the SDK's `GlobalSecondaryIndex[]`, but the two schemas model per-GSI throughput completely differently and the SDK v3 serializer silently drops unknown members — so a PROVISIONED-billing GlobalTable with a GSI failed `CreateTable` outright (AWS requires `ProvisionedThroughput` on every GSI) and every `TableV2` per-GSI on-demand limit vanished. `TableV2` is the recommended L2 since CDK 2.95, so this is a daily-pattern surface. **The issue's own mapping table turned out to be materially incomplete, and re-deriving it from the authoritative schemas changed the fix twice.** (1) `WriteProvisionedThroughputSettings` has EXACTLY ONE member, `WriteCapacityAutoScalingSettings` — there is no literal `WriteCapacityUnits`, because write capacity on a GlobalTable is always auto-scaled; `CreateTable` needs a concrete number, so the mapping takes `SeedCapacity` (the documented "initial provisioned capacity units") before `MinCapacity`. (2) Per-GSI READ capacity is not on the top-level GSI at all — CDK synthesizes it to `Replicas[?Region==<deploy region>].GlobalSecondaryIndexes[].Read{Provisioned,OnDemand}ThroughputSettings`, so the SDK's single `ProvisionedThroughput` / `OnDemandThroughput` object has to be FUSED from both halves (the GSI-level spellings the schema also permits are honored as a fallback for hand-authored templates). Both facts came from the live `cloudformation:DescribeType` schema cross-checked against a real `cdk synth` under both billing modes; the repo's captured fixture stores top-level names only and could not settle it. **Call sites:** `create()`, `addReplica()`, the `update()` replica-modify action, the `update()` GSI diff (both sides are now translated BEFORE diffing, so emitted `Create` / `Update` actions carry real throughput — a side benefit is that an auto-scaling-only edit, invisible to the DynamoDB API, no longer emits a bare `Update: {IndexName}` that AWS rejects as empty), and one site the issue did not name: the `PAY_PER_REQUEST -> PROVISIONED` **billing-mode flip**, where AWS requires per-index `ProvisionedThroughput` in the SAME `UpdateTable` call — without it the fix would have made create work while leaving the flip broken. **Deliberately left unmapped, recorded in a JSDoc block rather than dropped silently** (this provider has no `unhandledByDesign` map): `Replicas[].ReplicaStreamSpecification.ResourcePolicy` and `Replicas[].ResourcePolicy` (both need `PutResourcePolicy`, not any `UpdateTable` field) and `Replicas[].GlobalSecondaryIndexes[].ContributorInsightsSpecification` (needs a per-index `UpdateContributorInsights`). `handledProperties` is unchanged, so no coverage regeneration was needed. **Three defects in the first cut were caught by review and fixed here, each mutation-proofed:** (a) the billing flip built its index updates from the NEW template but filtered them by the PREVIOUS template's names, so an index the deploy REMOVES got no capacity — yet its `Delete` is issued later, so at flip time it is still a live index on a table becoming PROVISIONED and AWS rejects the whole call; (b) the first attempt at suppressing a false immutable-field warning skipped the `modified` loop wholesale on ANY flip, which silently dropped every per-GSI `Max{Read,Write}RequestUnits` on a `PROVISIONED -> PAY_PER_REQUEST` flip (the flip call carries per-GSI fields in one direction only) — the loop now sends the fields the NEW billing mode needs and suppresses only the warning; (c) a non-array `GlobalSecondaryIndexes` (an unresolved intrinsic) collapsed to `[]` and would have created the table with ZERO indexes while reporting success — the #1387 class one level up — and now throws. `WarmThroughput` also rides the update-ADD path, so a GSI added later matches what `create()` sends for the same template. **Tests:** 29 in a new file, with both property bags copied VERBATIM from a real `cdk synth` rather than hand-invented — which is precisely what surfaced the read-capacity-lives-on-the-replica asymmetry that a hand-written fixture would have encoded wrongly. Every call site and guard is mutation-proofed individually: reverting any one of them kills a specific test. **Integ:** the fixture gains two UNCONDITIONAL `TableV2`s (not gated behind `CDKD_TEST_UPDATE`, so the baseline deploy exercises the previously-failing create path) — two tables because the billing modes cannot coexist on one. L2 was correct here rather than L1, since `TableV2` exposes every property needed. `verify.sh` reads all four GSI values back plus the two table-level ones, and its step 3 stopped taking "the first GlobalTable in state" (which with three tables would have grabbed an arbitrary one) in favor of selecting by logical-id prefix.
0 commit comments