You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/changelog-cdkd.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,6 +16,9 @@ The CLAUDE.md `## Known Limitations` section retains the load-bearing summary
16
16
17
17
---
18
18
19
+
**Recently Implemented** (2026-08-10):
20
+
- ✅ **`AWS::DynamoDB::GlobalTable`: the TABLE-level on-demand write ceiling resets when the template drops it; the two REPLICA overrides are proven un-resettable and deliberately left alone (issue [#1434](https://github.com/go-to-k/cdkd/issues/1434))** — `src/provisioning/providers/dynamodb-globaltable-provider.ts` + `tests/unit/provisioning/dynamodb-globaltable-provider-gsi-throughput.test.ts`. **The class:** removing `WriteOnDemandThroughputSettings` from a template never set `flatChanged`, so no `UpdateTable` went out and the old ceiling stayed live in AWS while cdkd reported success — the absent-field-reset silent drop (#1160), the table-level sibling of the per-GSI case #1423 closed. **The fix** merges per member rather than branching on "the whole block disappeared" (the shape the #1433 review caught: a block can survive with its member dropped, which is the likelier user edit) and is SUPPRESSED while the billing mode is flipping, since dropping the on-demand block on the way to PROVISIONED is the natural template edit rather than a clear request, and step 4's flip owns that call. The `oldBilling` / `newBilling` pair is now resolved ONCE above step 3 and reused by step 4, so the two sites cannot disagree about what "flipping" means. **The issue asked for three fields; only ONE turned out to be implementable, and the live probes are what settled it** (us-east-1 source + us-west-2 replica, one global table per probe, both torn down — transcripts on the issue). Table-level `-1` behaves exactly as the issue assumed: `{MaxWriteRequestUnits: -1}` was accepted and `DescribeTable` then returned `{MaxReadRequestUnits: 100}`, i.e. the dropped member cleared and the untouched sibling preserved, with the reset reading back as ABSENCE never as -1 — verified independently rather than inherited from #1423, since reset semantics are field-specific. The two REPLICA overrides have NO reset mechanism at all: `OnDemandThroughputOverride: {MaxReadRequestUnits: -1}` is accepted but **stored literally as -1** on readback (writing it would be strictly worse than the current no-op — a nonsense value instead of a stale-but-valid one); the documented "empty override means inherit the source table" form `{}` is accepted and then **hangs the table in `UPDATING` for over an hour** with the override unchanged and every later call returning `ResourceInUseException` (it left a table AWS refused to delete for having "acted as a source region for new replica(s) ... in the last 24 hours" — treat `{}` as hazardous in any future probe); an entry carrying only `IndexName` is rejected outright (`ValidationException: There are no actions specified in the Replica Update Action`). `ProvisionedThroughputOverride` is the same story by construction — the registry schema declares `ReadCapacityUnits` / `MaxReadRequestUnits` `"minimum": 1`, and live `-1` / `0` both fail validation while `{}` returns `InternalServerError`. So no in-band sentinel exists, and shipping any of them would trade a silent no-op for a live defect. **Tests:** 6 units covering the full removal, the PARTIAL removal, value-changed (never the sentinel), stringly-typed coercion, billing-flip suppression, and the no-change redeploy; revert-proofed — neutering the reset fails exactly the first two while the other four stay green, since they pin behavior the change does not alter. **Related finding filed, not fixed here:** the probes surfaced that the table-level on-demand READ ceiling (`Replicas[local].ReadOnDemandThroughputSettings`, i.e. the canonical `Billing.onDemand({maxReadRequestUnits})`) is never wired AT ALL — dropped on the way in rather than merely un-reset, and the repo's own verbatim-`cdk synth` fixture already carried the value with no assertion over it (issue [#1436](https://github.com/go-to-k/cdkd/issues/1436)).
21
+
19
22
**Recently Implemented** (2026-08-09):
20
23
- ✅ **The two `handledProperties` wiring gaps the #1404 critic caught are closed by DECLARING the drop, not by faking a wire (issues [#1411](https://github.com/go-to-k/cdkd/issues/1411) / [#1412](https://github.com/go-to-k/cdkd/issues/1412))** — `src/provisioning/providers/ec2-provider.ts`, `src/provisioning/providers/logs-loggroup-provider.ts`, `scripts/gen-handled-property-wiring.ts` (both `HANDLED_WIRING_ALLOW_LIST` entries removed), `src/provisioning/property-coverage.generated.ts` + `docs/_generated/handled-property-wiring.{json,md}` regenerated, `.claude/rules/code-layout.md`, and the `tests/integration/vpc-nat-gateway/` fixture. **Both issues proposed "wire it into create"; for #1411 that turned out to be IMPOSSIBLE, and re-deriving it changed the fix.** `AWS::EC2::NatGateway.MaxDrainDurationSeconds` is not a `CreateNatGatewayRequest` member (`@aws-sdk/client-ec2` `models/models_1.d.ts`), and EC2 ships no `ModifyNatGateway*` operation at all — the ONLY two SDK inputs carrying the field are `DisassociateNatGatewayAddressRequest` (`models_5`) and `UnassignPrivateNatGatewayAddressRequest` (`models_7`), i.e. it is a per-call drain timeout for RELEASING secondary addresses, which is why the CFn registry schema lists it under `writeOnlyProperties`. cdkd's `updateNatGateway` rejects every NatGateway property change, so it never issues those two calls and has nowhere to deliver the value. Modelling it as a replacement trigger (the fallback the issue suggested) was rejected too: the registry schema does NOT list it under `createOnlyProperties`, so recreating a gateway on a drain-timeout change would diverge from CloudFormation and needlessly break the data plane. **`AWS::Logs::LogGroup.ResourcePolicyDocument`** took option 2 of #1412 for the reason the issue itself gives — it maps to the separate `AWS::Logs::ResourcePolicy` type whose `logs:PutResourcePolicy` is ACCOUNT-scoped, not per-log-group, so owning it from a log group's lifecycle would require inventing an ownership answer (which policy name to claim, what to do on delete when the account-wide policy may be shared, how to resolve two log groups declaring conflicting documents); managing the sibling resource remains the real feature. **Both are now `unhandledByDesign` with rationales.** Correcting a premise carried in both issues: neither provider sets `disableCcApiFallback`, so this does NOT hard-reject such templates via the #614 viability guard — it fires the #614 AUTO-ROUTE, provisioning the resource through Cloud Control API where AWS's own handler applies the value. That is strictly better than both the silent drop and a reject. Note on existing stacks: routing stickiness is `cc-api` -> `cc-api` ONLY (`provider-registry.ts` short-circuits on that value alone), so a resource already recorded `provisionedBy: 'sdk'` whose template sets one of these properties DOES re-evaluate and flip to Cloud Control on its next UPDATE — `deploy-engine.ts` does that deliberately. That is the intended outcome (the value starts being applied instead of dropped) and costs no physical-ID churn, since Cloud Control updates in place. **Tests:** 10 new units across two files (each asserts the property is absent from `handledProperties`, present in `unhandledByDesign` with a rationale, reported by `findSilentDropProperties`, and that `ProviderRegistry.getProviderFor` routes to CC for a template setting it and to SDK for one that does not), plus a guard that `CreateNatGateway` never carries the field and a fence that neither retired property may return to `handledProperties`; `tests/unit/scripts/gen-handled-property-wiring.test.ts` re-pointed its real-code stale-entry probes onto a still-live allow-list entry (`IAMAccessKeyProvider#Serial`). Revert-proofed: re-adding both properties to `handledProperties` fails 6 of 10 and makes `audit:handled-property-wiring:check` fail naming both. The critic now reports **0 gaps, 2 allow-listed** (down from 4). **Integ:** the `vpc-nat-gateway` fixture gains a SECOND, L1-only private NAT gateway setting the property, so the L2 gateway stays on the SDK path and the two together assert heterogeneous routing in one stack. The value is `writeOnlyProperties` and no EC2 API returns it, so a read-back-and-compare assertion is structurally impossible; `verify.sh` asserts the routing consequence instead (`provisionedBy == 'cc-api'` on the drain gateway, `'sdk'` on the plain one, a vacuity guard grepping the literal out of the stack file so the two cannot drift, both gateways live and `available` on AWS, and both gone after destroy).
21
24
- ✅ **`AWS::CodeBuild::Project`: seven nested `Source` / `Environment` / `Cache` sub-keys wired, plus the `SourceIdentifier` create failure (issue [#1386](https://github.com/go-to-k/cdkd/issues/1386))** — `src/provisioning/providers/codebuild-provider.ts`, unit tests, and the `tests/integration/ci-cd/` fixture. **The class:** `mapSource` / `mapProperties` build FRESH SDK objects, so every CFn sub-key they do not name is dropped by the SDK serializer with the deploy still reporting success — the #1373 nested-key class, in a provider not yet under that critic's `NESTED_KEY_TARGETS`. **Wired (create AND update — both share `mapProperties`, and the drop is WORSE on update because `UpdateProject` is read-modify-write, so an unnamed sub-key was actively WIPED off a live project):** `Source.Auth.{Type,Resource}` -> `auth`, `Source.GitSubmodulesConfig.FetchSubmodules` -> `gitSubmodulesConfig` and `Source.BuildStatusConfig.{Context,TargetUrl}` -> `buildStatusConfig` (both synthesized by the CDK L2, so daily-pattern surfaces), `Environment.Fleet.FleetArn` -> `fleet`, `Environment.DockerServer.{ComputeType,SecurityGroupIds}` -> `dockerServer`, `Cache.CacheNamespace` -> `cacheNamespace`. **Bonus fix found while wiring:** `Source.SourceIdentifier` was never mapped at all, and AWS REQUIRES it on every `SecondarySources` entry — so any template using secondary sources failed `CreateProject` outright (a loud failure, not a silent drop, which is why #1386's static sweep missed it). `SecondarySources` route through the same `mapSource` helper, so all four Source sub-keys cover them automatically (pinned by a test). **Read side:** `readCurrentState` reverse-maps each newly-wired sub-key emit-when-present, so the drift baseline stays symmetric with the AWS-current snapshot — except `Source.Auth`, deliberately excluded because `BatchGetProjects` echoes it back partially and emitting a partial shape would fire phantom drift on every project that sets it (documented in-code). **Not mapped:** `Environment.HostKernel` exists in the CFn registry schema but has NO member anywhere in the installed `@aws-sdk/client-codebuild` `dist-types` tree, so there is nothing to map it onto until an SDK bump; it is a NESTED key, so it can live in neither `unhandledByDesign` (top-level-only) nor `NESTED_KEY_TARGETS` (CodeBuild is not a target yet — tracked on #1393), and an in-code comment naming both is the record. `handledProperties` is unchanged (all seven sit under the already-declared top-levels `Source` / `Environment` / `Cache`), so `property-coverage.generated.ts` correctly needed no regeneration. **Verified against the authoritative schemas, not the issue text:** every SDK member name and nesting read off `@aws-sdk/client-codebuild`'s `models_0.d.ts`, every CFn spelling off the live `cloudformation:DescribeType` registry schema (the repo's captured `AWS-CodeBuild-Project.json` fixture stores top-level names only). **Live-probed before the fixture was written** — a CODEPIPELINE-typed source REJECTS both nested Source sub-blocks ("Git submodules config is not supported for CodePipeline source" / "Source type CODEPIPELINE does not support BuildStatusConfig"), so the pipeline-fed project cannot carry them; the fixture adds a SECOND standalone L1 `CfnProject` on a PUBLIC GitHub source, the cheapest shape that accepts both and needs no source credential. `verify.sh` asserts all four covered values off a SINGLE `batch-get-projects` blob (so a throttle cannot make some assertions pass while others silently vanish) and gone-checks the new project after destroy. `Environment.Fleet.FleetArn` (paid reserved-capacity fleet), `Environment.DockerServer` (billable docker server) and `Source.Auth` (needs a connected source credential) are unit-pinned but deliberately not in the fixture. **Tests:** 7 new provider units, binding-proofed by reverting the real provider (5 of the 7 fail without the fix; the 2 that pass both ways are the intentional "omits the members when unset" regression guards).
0 commit comments