@go-to-k/cdkd 0.159.3 → 0.160.0

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.
@@ -3354,7 +3354,8 @@ const STATE_SCHEMA_VERSIONS_READABLE = [
3354
3354
  3,
3355
3355
  4,
3356
3356
  5,
3357
- 6
3357
+ 6,
3358
+ 7
3358
3359
  ];
3359
3360
  /**
3360
3361
  * Returns true when a recorded `DeletionPolicy` should prevent cdkd from
@@ -3597,7 +3598,7 @@ var S3StateBackend = class {
3597
3598
  const { expectedEtag, migrateLegacy } = options;
3598
3599
  const body = {
3599
3600
  ...state,
3600
- version: 6,
3601
+ version: 7,
3601
3602
  stackName,
3602
3603
  region
3603
3604
  };
@@ -9864,20 +9865,25 @@ const PROPERTY_COVERAGE_BY_TYPE = new Map([
9864
9865
  //#endregion
9865
9866
  //#region src/provisioning/property-coverage.ts
9866
9867
  /**
9867
- * Helpers for cdkd's deploy-time property-coverage pre-flight check.
9868
+ * Helpers for cdkd's deploy-time property-coverage check.
9868
9869
  *
9869
9870
  * The data ({@link PROPERTY_COVERAGE_BY_TYPE}) is generated by
9870
9871
  * `scripts/gen-property-coverage.ts` (run via `vp run gen:property-coverage`)
9871
9872
  * from the CFn schema fixtures (`tests/fixtures/cfn-schemas/*.json`) and
9872
9873
  * each SDK provider's `handledProperties` / `unhandledByDesign` declarations.
9873
9874
  * This module adds the runtime predicates + the actionable issue link used
9874
- * by the pre-flight check (see {@link ./provider-registry.ProviderRegistry.validateResourceProperties}).
9875
+ * by the routing decision (see
9876
+ * {@link ./provider-registry.ProviderRegistry.getProviderFor} and
9877
+ * {@link ./provider-registry.ProviderRegistry.reportSilentDropDecisions}).
9875
9878
  *
9876
- * The pre-flight rejects deploys whose templates use top-level CFn properties
9877
- * for which cdkd's SDK provider does not write to AWS (= silent drop). The
9878
- * user can opt in to the silent drop on a per-property basis via
9879
- * `--allow-unsupported-properties <Type:Prop>,...`. v0 stance: silent drop
9880
- * is a bug; explicit opt-in is required to proceed.
9879
+ * Behavior: when a template uses a top-level CFn property cdkd's SDK
9880
+ * Provider does not write to AWS (= silent drop), the resource is
9881
+ * **auto-routed via Cloud Control API** by default CC forwards the
9882
+ * full property map verbatim, closing the silent-drop bug (#614). The
9883
+ * user can opt out per-property via
9884
+ * `--allow-unsupported-properties <Type:Prop>,...`, which keeps the
9885
+ * resource on the SDK Provider path and accepts the silent drop (a
9886
+ * warn line is logged for auditability).
9881
9887
  */
9882
9888
  /**
9883
9889
  * Look up a Tier 1 type's property-coverage record. Returns `undefined` for
@@ -9914,24 +9920,48 @@ function findSilentDropProperties(resourceType, templateProperties) {
9914
9920
  return drops.sort((a, b) => a.property.localeCompare(b.property));
9915
9921
  }
9916
9922
  /**
9917
- * A 1-click pre-filled GitHub issue link requesting cdkd support for a
9918
- * specific top-level property on a resource type. Surfaced in the pre-flight
9919
- * error so a user hitting a silent drop lands directly in the "request
9920
- * support" flow.
9921
- */
9922
- function unsupportedPropertyIssueUrl(resourceType, property) {
9923
- return `https://github.com/go-to-k/cdkd/issues/new?title=${encodeURIComponent(`Support property ${resourceType}.${property}`)}&labels=resource-support`;
9923
+ * Same as {@link findSilentDropProperties} but filters out entries whose
9924
+ * `<Type>:<Prop>` key is in the supplied allow set (the
9925
+ * `--allow-unsupported-properties` user override). Returned drops are the
9926
+ * ones that should drive CC API auto-routing (issue
9927
+ * [#614](https://github.com/go-to-k/cdkd/issues/614)) — silent drops
9928
+ * the user has explicitly opted-into via the override are removed so the
9929
+ * resource stays on the SDK Provider path.
9930
+ *
9931
+ * Mirrors `findSilentDropProperties`'s sort + early-return behavior:
9932
+ * returns `[]` for Tier 2 / Custom / unknown types, undefined / empty
9933
+ * `templateProperties`, or when every drop is in `allowedKeys`.
9934
+ */
9935
+ function findActionableSilentDrops(resourceType, templateProperties, allowedKeys) {
9936
+ const drops = findSilentDropProperties(resourceType, templateProperties);
9937
+ if (drops.length === 0) return drops;
9938
+ return drops.filter(({ property }) => !allowedKeys.has(`${resourceType}:${property}`));
9924
9939
  }
9925
9940
 
9926
9941
  //#endregion
9927
9942
  //#region src/provisioning/provider-registry.ts
9928
9943
  /**
9929
- * Provider registry for managing resource providers
9930
- *
9931
- * Implements a fallback strategy:
9932
- * 1. Try specific SDK provider if registered for this resource type
9933
- * 2. Fall back to Cloud Control API if resource type is supported
9934
- * 3. Throw error if no provider available
9944
+ * Provider registry for managing resource providers.
9945
+ *
9946
+ * Selection strategy for a fresh resource (see {@link getProviderFor}):
9947
+ * 1. Custom Resource (`Custom::*` / `AWS::CloudFormation::CustomResource`)
9948
+ * Custom Resource provider (recorded as `provisionedBy: 'sdk'`).
9949
+ * 2. Existing-state `provisionedBy: 'cc-api'` Cloud Control (sticky).
9950
+ * 3. SDK Provider registered, no silent-drop properties (after the
9951
+ * `--allow-unsupported-properties` override filter) → SDK Provider.
9952
+ * 4. SDK Provider registered, silent-drop properties present, NOT all
9953
+ * in the allow set → Cloud Control (auto-route, info-logged).
9954
+ * 5. SDK Provider registered, silent-drop properties present, ALL in
9955
+ * the allow set → SDK Provider (the user explicitly accepted the
9956
+ * silent drop, warn-logged).
9957
+ * 6. No SDK Provider, Cloud Control supports the type → Cloud Control.
9958
+ * 7. `--allow-unsupported-types` escape hatch → Cloud Control optimistically.
9959
+ * 8. Otherwise → throw (no provider available).
9960
+ *
9961
+ * Tier 3 (`NON_PROVISIONABLE`) types are rejected earlier by
9962
+ * {@link validateResourceTypes}; the silent-drop auto-route only fires for
9963
+ * Tier 1 types whose SDK Provider declares `handledProperties` and where
9964
+ * Cloud Control is guaranteed to be a viable alternative.
9935
9965
  */
9936
9966
  var ProviderRegistry = class {
9937
9967
  logger = getLogger().child("ProviderRegistry");
@@ -9961,10 +9991,12 @@ var ProviderRegistry = class {
9961
9991
  /**
9962
9992
  * Escape hatch for the `--allow-unsupported-properties` CLI flag. Each entry
9963
9993
  * is a `<ResourceType>:<PropertyName>` token (e.g.
9964
- * `AWS::Lambda::Function:LoggingConfig`). Named entries bypass the
9965
- * property-level silent-drop pre-flight reject for that exact type+property
9966
- * pair. Per-type-property (not blanket) so the user explicitly acknowledges
9967
- * each silent drop they accept.
9994
+ * `AWS::Lambda::Function:LoggingConfig`). As of issue
9995
+ * [#614](https://github.com/go-to-k/cdkd/issues/614), the flag now means
9996
+ * "force the SDK Provider path and accept the silent drop" — the default
9997
+ * for an un-flagged silent-drop property is to auto-route the resource
9998
+ * through Cloud Control instead. Per-type-property (not blanket) so the
9999
+ * user explicitly acknowledges each silent drop they accept.
9968
10000
  */
9969
10001
  allowUnsupportedProperties(entries) {
9970
10002
  for (const entry of entries) {
@@ -10007,38 +10039,84 @@ var ProviderRegistry = class {
10007
10039
  this.providers.delete(resourceType);
10008
10040
  }
10009
10041
  /**
10010
- * Get provider for a resource type
10042
+ * Resolve the provider for a resource using the full routing decision
10043
+ * matrix (see class docstring). The returned object carries the chosen
10044
+ * provider, the `provisionedBy` layer label to persist on the resource's
10045
+ * state record, and (for the CC auto-route case) the names of the
10046
+ * silent-drop properties that drove the decision so callers can render
10047
+ * `[via CC API: <reason>]` plan annotations.
10011
10048
  *
10012
- * Selection strategy:
10013
- * 1. If specific SDK provider is registered, use it
10014
- * 2. Otherwise, use Cloud Control API if supported
10015
- * 3. Throw error if no provider available
10016
- *
10017
- * @param resourceType CloudFormation resource type
10018
- * @returns Provider instance
10019
- * @throws Error if no provider available
10049
+ * @throws Error if no provider can be found for the type.
10020
10050
  */
10021
- getProvider(resourceType) {
10051
+ getProviderFor(input) {
10052
+ const { resourceType, properties, provisionedBy } = input;
10053
+ if (isCustomResource(resourceType)) {
10054
+ this.logger.debug(`Using Custom Resource provider for ${resourceType}`);
10055
+ return {
10056
+ provider: this.customResourceProvider,
10057
+ provisionedBy: "sdk"
10058
+ };
10059
+ }
10060
+ if (provisionedBy === "cc-api") {
10061
+ this.logger.debug(`Routing ${resourceType} via Cloud Control (state-recorded provisionedBy=cc-api)`);
10062
+ return {
10063
+ provider: this.cloudControlProvider,
10064
+ provisionedBy: "cc-api"
10065
+ };
10066
+ }
10022
10067
  const specificProvider = this.providers.get(resourceType);
10023
10068
  if (specificProvider) {
10024
- this.logger.debug(`Using specific SDK provider for ${resourceType}`);
10025
- return specificProvider;
10069
+ const actionableDrops = findActionableSilentDrops(resourceType, properties, this.allowedUnsupportedProperties);
10070
+ if (actionableDrops.length === 0) {
10071
+ this.logger.debug(`Using specific SDK provider for ${resourceType}`);
10072
+ return {
10073
+ provider: specificProvider,
10074
+ provisionedBy: "sdk"
10075
+ };
10076
+ }
10077
+ this.logger.debug(`Auto-routing ${resourceType} via Cloud Control (silent-drop properties: ${actionableDrops.map((d) => d.property).join(", ")})`);
10078
+ return {
10079
+ provider: this.cloudControlProvider,
10080
+ provisionedBy: "cc-api",
10081
+ ccRouteReason: { properties: actionableDrops.map((d) => d.property) }
10082
+ };
10026
10083
  }
10027
10084
  if (CloudControlProvider.isSupportedResourceType(resourceType)) {
10028
10085
  this.logger.debug(`Using Cloud Control API provider for ${resourceType}`);
10029
- return this.cloudControlProvider;
10030
- }
10031
- if (resourceType.startsWith("Custom::") || resourceType === "AWS::CloudFormation::CustomResource") {
10032
- this.logger.debug(`Using Custom Resource provider for ${resourceType}`);
10033
- return this.customResourceProvider;
10086
+ return {
10087
+ provider: this.cloudControlProvider,
10088
+ provisionedBy: "cc-api"
10089
+ };
10034
10090
  }
10035
10091
  if (this.allowedUnsupportedTypes.has(resourceType)) {
10036
10092
  this.logger.debug(`Routing escape-hatch-allowed type ${resourceType} through Cloud Control API`);
10037
- return this.cloudControlProvider;
10093
+ return {
10094
+ provider: this.cloudControlProvider,
10095
+ provisionedBy: "cc-api"
10096
+ };
10038
10097
  }
10039
10098
  throw new Error(`No provider available for resource type: ${resourceType}. This resource type is not supported by Cloud Control API and no SDK provider is registered.`);
10040
10099
  }
10041
10100
  /**
10101
+ * Legacy entry point that returns just the provider. Delegates to
10102
+ * {@link getProviderFor} with no properties / no state-recorded layer —
10103
+ * which means silent-drop auto-routing CANNOT fire (no template to
10104
+ * inspect) and `provisionedBy === undefined` is treated as SDK semantics
10105
+ * (legacy default). Use {@link getProviderFor} when the caller has
10106
+ * properties / state — otherwise a CC-managed existing resource will get
10107
+ * an SDK Provider on its update / delete path, which is the
10108
+ * silent-data-corruption hazard that v7's schema bump is meant to
10109
+ * prevent.
10110
+ *
10111
+ * Kept on the public surface for the destroy / drift / state-refresh
10112
+ * paths whose call sites only know the resource type (the caller should
10113
+ * still thread `provisionedBy` from state when it's available; this
10114
+ * shape is only safe for type-only callers).
10115
+ */
10116
+ getProvider(resourceType) {
10117
+ return this.getProviderFor({ resourceType }).provider;
10118
+ }
10119
+ /**
10042
10120
  * Check if a resource type should be skipped
10043
10121
  */
10044
10122
  shouldSkipResource(resourceType) {
@@ -10050,7 +10128,7 @@ var ProviderRegistry = class {
10050
10128
  hasProvider(resourceType) {
10051
10129
  if (this.shouldSkipResource(resourceType)) return true;
10052
10130
  if (this.allowedUnsupportedTypes.has(resourceType)) return true;
10053
- return this.providers.has(resourceType) || CloudControlProvider.isSupportedResourceType(resourceType) || resourceType.startsWith("Custom::") || resourceType === "AWS::CloudFormation::CustomResource";
10131
+ return this.providers.has(resourceType) || CloudControlProvider.isSupportedResourceType(resourceType) || isCustomResource(resourceType);
10054
10132
  }
10055
10133
  /**
10056
10134
  * Get the Cloud Control provider instance (for resource state lookup)
@@ -10095,77 +10173,96 @@ var ProviderRegistry = class {
10095
10173
  this.logger.debug(`Validated ${resourceTypes.size} resource types: all have available providers`);
10096
10174
  }
10097
10175
  /**
10098
- * Pre-flight reject: walk every resource in the template and identify
10099
- * top-level CFn properties cdkd's SDK provider would silently drop on
10100
- * write. Throws with a per-resource per-property breakdown + the exact
10101
- * `--allow-unsupported-properties` re-run command. No-op for Tier 2 (Cloud
10102
- * Control) types CC forwards the full property map to AWS, so cdkd has
10103
- * no write-side silent drop for those.
10176
+ * Walk every resource in the template and identify top-level CFn
10177
+ * properties cdkd's SDK provider would silently drop on write. As of
10178
+ * issue [#614](https://github.com/go-to-k/cdkd/issues/614), this method
10179
+ * **no longer throws** — silent drops now auto-route the resource through
10180
+ * Cloud Control API by default (see {@link getProviderFor}). The method
10181
+ * is retained on the name `validateResourceProperties` so existing deploy
10182
+ * call sites continue to work; it now emits info-level routing decisions
10183
+ * for each silent-drop resource, plus warn-level lines for resources
10184
+ * where the user explicitly opted into the silent drop via
10185
+ * `--allow-unsupported-properties`.
10104
10186
  *
10105
10187
  * Must be called AFTER {@link validateResourceTypes} — type-level errors
10106
- * are reported first. For a type allowed via `--allow-unsupported-types`,
10107
- * the type-level check passes and this property check is a no-op
10108
- * (`findSilentDropProperties` returns `[]` for non-Tier-1 / unknown types).
10188
+ * are still hard rejects. For a type allowed via `--allow-unsupported-types`,
10189
+ * the property check is a no-op (`findSilentDropProperties` returns `[]`
10190
+ * for non-Tier-1 / unknown types).
10191
+ *
10192
+ * @see findAutoRouteHits for the pure-functional pre-deploy plan-builder
10193
+ * that returns the same information without logging.
10109
10194
  */
10110
10195
  validateResourceProperties(resources) {
10111
- const errors = [];
10112
- for (const { logicalId, resourceType, properties } of resources) {
10196
+ this.reportSilentDropDecisions(resources);
10197
+ }
10198
+ /**
10199
+ * Info-log every silent-drop routing decision (auto-route via CC API) and
10200
+ * warn-log every silent drop the user explicitly opted into via
10201
+ * `--allow-unsupported-properties` (forced SDK path, the property will
10202
+ * be dropped). Pure side-effect — does not mutate state and never throws.
10203
+ *
10204
+ * Issue [#614](https://github.com/go-to-k/cdkd/issues/614). Replaces the
10205
+ * pre-v0.16x throw path: silent drops are now a routing signal, not an
10206
+ * error.
10207
+ *
10208
+ * When the optional `provisionedBy` (from existing state) is `'cc-api'`,
10209
+ * the auto-route info line is demoted to `debug` — the resource has been
10210
+ * on CC for at least one prior deploy, so the routing decision is
10211
+ * **continuation of sticky state, not a fresh auto-route**. Surfacing the
10212
+ * info line every deploy would be repetitive noise. The warn line for
10213
+ * explicit `--allow-unsupported-properties` overrides is NOT demoted —
10214
+ * that override is an active user choice for THIS deploy and should
10215
+ * surface every time.
10216
+ */
10217
+ reportSilentDropDecisions(resources) {
10218
+ for (const { logicalId, resourceType, properties, provisionedBy } of resources) {
10113
10219
  const drops = findSilentDropProperties(resourceType, properties);
10114
- for (const { property, rationale } of drops) {
10220
+ if (drops.length === 0) continue;
10221
+ const overridden = [];
10222
+ const autoRouted = [];
10223
+ for (const { property } of drops) {
10115
10224
  const allowKey = `${resourceType}:${property}`;
10116
- if (this.allowedUnsupportedProperties.has(allowKey)) continue;
10117
- errors.push({
10118
- logicalId,
10119
- resourceType,
10120
- property,
10121
- rationale
10122
- });
10225
+ if (this.allowedUnsupportedProperties.has(allowKey)) overridden.push(property);
10226
+ else autoRouted.push(property);
10227
+ }
10228
+ if (autoRouted.length > 0) {
10229
+ const message = `${logicalId} (${resourceType}): routing via Cloud Control API (cdkd's SDK Provider does not yet wire ${autoRouted.join(", ")} — CC API will forward the full property map. Override via --allow-unsupported-properties ${autoRouted.map((p) => `${resourceType}:${p}`).join(",")}.)`;
10230
+ if (provisionedBy === "cc-api") this.logger.debug(message);
10231
+ else this.logger.info(message);
10232
+ }
10233
+ if (overridden.length > 0) {
10234
+ const propList = overridden.join(", ");
10235
+ this.logger.warn(`${logicalId} (${resourceType}): ${propList} will be silently dropped (--allow-unsupported-properties override accepted). Remove the override to route this resource via Cloud Control API instead.`);
10123
10236
  }
10124
10237
  }
10125
- if (errors.length === 0) return;
10126
- throw new Error(renderPropertyCoverageError(errors));
10127
10238
  }
10128
- };
10129
- /**
10130
- * Render the actionable pre-flight error for property-level silent drops.
10131
- * Groups by logical ID, sorts properties within each resource, and emits
10132
- * a comma-joined `--allow-unsupported-properties` re-run command with
10133
- * deduplicated `Type:Prop` entries (the same type appearing in two
10134
- * resources only needs one entry — the flag is per-type-prop, not
10135
- * per-resource).
10136
- */
10137
- function renderPropertyCoverageError(errors) {
10138
- const byLogicalId = /* @__PURE__ */ new Map();
10139
- for (const e of errors) {
10140
- let entry = byLogicalId.get(e.logicalId);
10141
- if (!entry) {
10142
- entry = {
10143
- resourceType: e.resourceType,
10144
- props: []
10145
- };
10146
- byLogicalId.set(e.logicalId, entry);
10239
+ /**
10240
+ * Pure-functional discovery of every resource whose template uses one or
10241
+ * more silent-drop properties that are NOT in the
10242
+ * `--allow-unsupported-properties` allow set i.e. every resource that
10243
+ * {@link getProviderFor} would auto-route via Cloud Control. Returned
10244
+ * entries carry the silent-drop property names so plan / diff renderers
10245
+ * can show `[via CC API: LoggingConfig]`.
10246
+ *
10247
+ * Does NOT log or throw. Use {@link reportSilentDropDecisions} for the
10248
+ * side-effecting info / warn surface.
10249
+ */
10250
+ findAutoRouteHits(resources) {
10251
+ const hits = [];
10252
+ for (const { logicalId, resourceType, properties } of resources) {
10253
+ const actionable = findActionableSilentDrops(resourceType, properties, this.allowedUnsupportedProperties);
10254
+ if (actionable.length === 0) continue;
10255
+ hits.push({
10256
+ logicalId,
10257
+ resourceType,
10258
+ properties: actionable.map((d) => d.property)
10259
+ });
10147
10260
  }
10148
- entry.props.push({
10149
- property: e.property,
10150
- rationale: e.rationale
10151
- });
10261
+ return hits;
10152
10262
  }
10153
- const sections = [];
10154
- const sortedLogicalIds = [...byLogicalId.keys()].sort((a, b) => a.localeCompare(b));
10155
- for (const logicalId of sortedLogicalIds) {
10156
- const { resourceType, props } = byLogicalId.get(logicalId);
10157
- const propLines = [...props].sort((a, b) => a.property.localeCompare(b.property)).map(({ property, rationale }) => {
10158
- return ` - ${property}\n ${rationale}\n Request support: ${unsupportedPropertyIssueUrl(resourceType, property)}`;
10159
- }).join("\n");
10160
- sections.push(` ${logicalId} (${resourceType}):\n${propLines}`);
10161
- }
10162
- const dedupRerun = Array.from(new Set(errors.map((e) => `${e.resourceType}:${e.property}`))).join(",");
10163
- return `cdkd would silently drop these properties at deploy time:\n\n` + sections.join("\n\n") + `
10164
-
10165
- These properties exist in your CDK code but cdkd will not write them to AWS. The deployed resource will be missing these fields.
10166
-
10167
- To proceed anyway (accepts the silent drop), re-run with:
10168
- --allow-unsupported-properties ${dedupRerun}`;
10263
+ };
10264
+ function isCustomResource(resourceType) {
10265
+ return resourceType.startsWith("Custom::") || resourceType === "AWS::CloudFormation::CustomResource";
10169
10266
  }
10170
10267
 
10171
10268
  //#endregion
@@ -11505,7 +11602,7 @@ var DeployEngine = class {
11505
11602
  try {
11506
11603
  const currentStateData = await this.stateBackend.getState(stackName, this.stackRegion);
11507
11604
  const currentState = currentStateData?.state ?? {
11508
- version: 6,
11605
+ version: 7,
11509
11606
  region: this.stackRegion,
11510
11607
  stackName,
11511
11608
  resources: {},
@@ -11532,7 +11629,8 @@ var DeployEngine = class {
11532
11629
  const resourcesForPropertyCheck = Object.entries(template.Resources || {}).filter(([, r]) => r.Type !== "AWS::CDK::Metadata").map(([logicalId, r]) => ({
11533
11630
  logicalId,
11534
11631
  resourceType: r.Type,
11535
- properties: r.Properties
11632
+ properties: r.Properties,
11633
+ provisionedBy: currentState.resources[logicalId]?.provisionedBy
11536
11634
  }));
11537
11635
  this.providerRegistry.validateResourceProperties(resourcesForPropertyCheck);
11538
11636
  this.logger.debug(`All resource properties validated`);
@@ -11553,7 +11651,7 @@ var DeployEngine = class {
11553
11651
  await this.drainObservedCaptures(currentState.resources);
11554
11652
  try {
11555
11653
  const refreshedState = {
11556
- version: 6,
11654
+ version: 7,
11557
11655
  region: this.stackRegion,
11558
11656
  stackName: currentState.stackName,
11559
11657
  resources: currentState.resources,
@@ -11653,7 +11751,7 @@ var DeployEngine = class {
11653
11751
  saveChain = saveChain.then(async () => {
11654
11752
  try {
11655
11753
  const partialState = {
11656
- version: 6,
11754
+ version: 7,
11657
11755
  region: this.stackRegion,
11658
11756
  stackName: currentState.stackName,
11659
11757
  resources: newResources,
@@ -11711,6 +11809,7 @@ var DeployEngine = class {
11711
11809
  logicalId,
11712
11810
  changeType: change.changeType,
11713
11811
  resourceType: change.resourceType,
11812
+ provisionedBy: newResources[logicalId]?.provisionedBy ?? previousState?.provisionedBy,
11714
11813
  previousState,
11715
11814
  physicalId: newResources[logicalId]?.physicalId,
11716
11815
  properties: newResources[logicalId]?.properties
@@ -11747,6 +11846,7 @@ var DeployEngine = class {
11747
11846
  logicalId,
11748
11847
  changeType: "DELETE",
11749
11848
  resourceType: change.resourceType,
11849
+ provisionedBy: previousState?.provisionedBy,
11750
11850
  previousState
11751
11851
  });
11752
11852
  saveStateAfterResource(logicalId);
@@ -11759,7 +11859,7 @@ var DeployEngine = class {
11759
11859
  } catch (error) {
11760
11860
  try {
11761
11861
  const preRollbackState = {
11762
- version: 6,
11862
+ version: 7,
11763
11863
  region: this.stackRegion,
11764
11864
  stackName: currentState.stackName,
11765
11865
  resources: newResources,
@@ -11788,7 +11888,7 @@ var DeployEngine = class {
11788
11888
  } else await this.performRollback(completedOperations, newResources, stackName);
11789
11889
  try {
11790
11890
  const postRollbackState = {
11791
- version: 6,
11891
+ version: 7,
11792
11892
  region: this.stackRegion,
11793
11893
  stackName: currentState.stackName,
11794
11894
  resources: newResources,
@@ -11803,7 +11903,7 @@ var DeployEngine = class {
11803
11903
  try {
11804
11904
  const freshEtag = (await this.stateBackend.getState(stackName, this.stackRegion))?.etag;
11805
11905
  const postRollbackState = {
11806
- version: 6,
11906
+ version: 7,
11807
11907
  region: this.stackRegion,
11808
11908
  stackName: currentState.stackName,
11809
11909
  resources: newResources,
@@ -11822,7 +11922,7 @@ var DeployEngine = class {
11822
11922
  const outputs = await this.resolveOutputs(template, newResources, stackName, parameterValues, conditions);
11823
11923
  return {
11824
11924
  state: {
11825
- version: 6,
11925
+ version: 7,
11826
11926
  region: this.stackRegion,
11827
11927
  stackName: currentState.stackName,
11828
11928
  resources: newResources,
@@ -11922,23 +12022,31 @@ var DeployEngine = class {
11922
12022
  async performSingleRollback(op, stateResources) {
11923
12023
  try {
11924
12024
  switch (op.changeType) {
11925
- case "CREATE":
12025
+ case "CREATE": {
11926
12026
  if (!op.physicalId) {
11927
12027
  this.logger.warn(` Rollback: Cannot delete ${op.logicalId} — no physical ID recorded`);
11928
12028
  break;
11929
12029
  }
11930
12030
  this.logger.info(` Rollback: Deleting created resource ${op.logicalId} (${op.resourceType})`);
11931
- await this.providerRegistry.getProvider(op.resourceType).delete(op.logicalId, op.physicalId, op.resourceType, op.properties, { expectedRegion: this.stackRegion });
12031
+ const { provider } = this.providerRegistry.getProviderFor({
12032
+ resourceType: op.resourceType,
12033
+ provisionedBy: op.provisionedBy
12034
+ });
12035
+ await provider.delete(op.logicalId, op.physicalId, op.resourceType, op.properties, { expectedRegion: this.stackRegion });
11932
12036
  delete stateResources[op.logicalId];
11933
12037
  this.logger.info(` Rollback: ${op.logicalId} deleted successfully`);
11934
12038
  break;
12039
+ }
11935
12040
  case "UPDATE": {
11936
12041
  if (!op.previousState) {
11937
12042
  this.logger.warn(` Rollback: Cannot restore ${op.logicalId} — no previous state available`);
11938
12043
  break;
11939
12044
  }
11940
12045
  this.logger.info(` Rollback: Restoring ${op.logicalId} (${op.resourceType}) to previous state`);
11941
- const provider = this.providerRegistry.getProvider(op.resourceType);
12046
+ const { provider } = this.providerRegistry.getProviderFor({
12047
+ resourceType: op.resourceType,
12048
+ provisionedBy: op.provisionedBy
12049
+ });
11942
12050
  const currentResource = stateResources[op.logicalId];
11943
12051
  if (!currentResource) {
11944
12052
  this.logger.warn(` Rollback: Cannot restore ${op.logicalId} — resource not found in current state`);
@@ -12005,7 +12113,7 @@ var DeployEngine = class {
12005
12113
  */
12006
12114
  async provisionResourceBody(logicalId, change, stateResources, stackName, template, parameterValues, conditions, counts, progress) {
12007
12115
  const resourceType = change.resourceType;
12008
- const provider = this.providerRegistry.getProvider(resourceType);
12116
+ const existingState = stateResources[logicalId];
12009
12117
  const renderer = getLiveRenderer();
12010
12118
  switch (change.changeType) {
12011
12119
  case "CREATE": {
@@ -12017,8 +12125,13 @@ var DeployEngine = class {
12017
12125
  ...conditions && { conditions }
12018
12126
  }, stackName);
12019
12127
  const resolvedProps = await this.resolver.resolve(desiredProps, context);
12020
- const { provider: createProvider, properties: createProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
12021
- const result = await this.withRetry(() => createProvider.create(logicalId, resourceType, createProps), logicalId, void 0, void 0, provider);
12128
+ const createDecision = this.providerRegistry.getProviderFor({
12129
+ resourceType,
12130
+ properties: resolvedProps
12131
+ });
12132
+ const createProvider = createDecision.provider;
12133
+ const createProps = createDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
12134
+ const result = await this.withRetry(() => createProvider.create(logicalId, resourceType, createProps), logicalId, void 0, void 0, createProvider);
12022
12135
  const dependencies = this.extractAllDependencies(template, logicalId);
12023
12136
  const templateAttrs = this.extractTemplateAttributes(template, logicalId);
12024
12137
  stateResources[logicalId] = {
@@ -12027,9 +12140,10 @@ var DeployEngine = class {
12027
12140
  properties: resolvedProps,
12028
12141
  ...result.attributes && { attributes: result.attributes },
12029
12142
  ...dependencies && dependencies.length > 0 && { dependencies },
12030
- ...templateAttrs
12143
+ ...templateAttrs,
12144
+ provisionedBy: createDecision.provisionedBy
12031
12145
  };
12032
- this.kickOffObservedCapture(provider, logicalId, result.physicalId, resourceType, resolvedProps);
12146
+ this.kickOffObservedCapture(createProvider, logicalId, result.physicalId, resourceType, resolvedProps);
12033
12147
  if (counts) counts.created++;
12034
12148
  if (progress) progress.current++;
12035
12149
  const createPrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
@@ -12038,7 +12152,7 @@ var DeployEngine = class {
12038
12152
  break;
12039
12153
  }
12040
12154
  case "UPDATE": {
12041
- const currentResource = stateResources[logicalId];
12155
+ const currentResource = existingState;
12042
12156
  if (!currentResource) throw new Error(`Cannot update ${logicalId}: resource not found in state`);
12043
12157
  const desiredProps = change.desiredProperties || {};
12044
12158
  const currentProps = change.currentProperties || {};
@@ -12073,14 +12187,24 @@ var DeployEngine = class {
12073
12187
  if (needsReplacement) {
12074
12188
  const replacedProps = change.propertyChanges?.filter((pc) => pc.requiresReplacement).map((pc) => pc.path).join(", ");
12075
12189
  this.logger.info(`Replacing ${logicalId} (${resourceType}) - immutable properties changed: ${replacedProps}`);
12190
+ const replaceDecision = this.providerRegistry.getProviderFor({
12191
+ resourceType,
12192
+ properties: resolvedProps
12193
+ });
12194
+ const replaceProvider = replaceDecision.provider;
12195
+ const replaceProps = replaceDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
12076
12196
  this.logger.info(` Creating new ${logicalId}...`);
12077
- const { provider: replaceProvider, properties: replaceProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
12078
- const createResult = await this.withRetry(() => replaceProvider.create(logicalId, resourceType, replaceProps), logicalId, void 0, void 0, provider);
12079
- if (template?.Resources?.[logicalId]?.UpdateReplacePolicy === "Retain") this.logger.info(` Retaining old ${logicalId} (${currentResource.physicalId}) - UpdateReplacePolicy: Retain`);
12197
+ const createResult = await this.withRetry(() => replaceProvider.create(logicalId, resourceType, replaceProps), logicalId, void 0, void 0, replaceProvider);
12198
+ const updateReplacePolicy = template?.Resources?.[logicalId]?.UpdateReplacePolicy;
12199
+ const oldDeleteProvider = this.providerRegistry.getProviderFor({
12200
+ resourceType,
12201
+ provisionedBy: currentResource.provisionedBy
12202
+ }).provider;
12203
+ if (updateReplacePolicy === "Retain") this.logger.info(` Retaining old ${logicalId} (${currentResource.physicalId}) - UpdateReplacePolicy: Retain`);
12080
12204
  else {
12081
12205
  this.logger.info(` Deleting old ${logicalId} (${currentResource.physicalId})...`);
12082
12206
  try {
12083
- await provider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, { expectedRegion: this.stackRegion });
12207
+ await oldDeleteProvider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, { expectedRegion: this.stackRegion });
12084
12208
  this.logger.info(` ${green("✓")} Old resource deleted`);
12085
12209
  } catch (deleteError) {
12086
12210
  this.logger.warn(` ⚠ Failed to delete old resource ${logicalId} (${currentResource.physicalId}): ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`);
@@ -12092,9 +12216,10 @@ var DeployEngine = class {
12092
12216
  properties: resolvedProps,
12093
12217
  ...createResult.attributes && { attributes: createResult.attributes },
12094
12218
  ...dependencies && dependencies.length > 0 && { dependencies },
12095
- ...this.extractTemplateAttributes(template, logicalId)
12219
+ ...this.extractTemplateAttributes(template, logicalId),
12220
+ provisionedBy: replaceDecision.provisionedBy
12096
12221
  };
12097
- this.kickOffObservedCapture(provider, logicalId, createResult.physicalId, resourceType, resolvedProps);
12222
+ this.kickOffObservedCapture(replaceProvider, logicalId, createResult.physicalId, resourceType, resolvedProps);
12098
12223
  if (counts) counts.updated++;
12099
12224
  if (progress) progress.current++;
12100
12225
  const replacePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
@@ -12102,28 +12227,41 @@ var DeployEngine = class {
12102
12227
  this.logger.info(`${replacePrefix}${yellow("↻")} ${bold(logicalId)} ${gray(`(${resourceType})`)} ${yellow("replaced")}`);
12103
12228
  } else {
12104
12229
  this.logger.debug(`Updating ${logicalId} (${resourceType})`);
12105
- const { provider: updateProvider, properties: updateProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
12230
+ const updateDecision = this.providerRegistry.getProviderFor({
12231
+ resourceType,
12232
+ properties: resolvedProps,
12233
+ provisionedBy: currentResource.provisionedBy
12234
+ });
12235
+ const updateProvider = updateDecision.provider;
12236
+ const updateProps = updateDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
12106
12237
  let result;
12238
+ let resultProvisionedBy = updateDecision.provisionedBy;
12107
12239
  try {
12108
- result = await this.withRetry(() => updateProvider.update(logicalId, currentResource.physicalId, resourceType, updateProps, currentProps), logicalId, void 0, void 0, provider);
12240
+ result = await this.withRetry(() => updateProvider.update(logicalId, currentResource.physicalId, resourceType, updateProps, currentProps), logicalId, void 0, void 0, updateProvider);
12109
12241
  } catch (updateError) {
12110
12242
  const msg = updateError instanceof Error ? updateError.message : String(updateError);
12111
12243
  if (msg.includes("UnsupportedActionException") || msg.includes("does not support UPDATE")) {
12112
12244
  this.logger.info(`UPDATE not supported for ${logicalId} (${resourceType}), replacing (DELETE → CREATE)`);
12113
12245
  try {
12114
- await provider.delete(logicalId, currentResource.physicalId, resourceType, currentProps, { expectedRegion: this.stackRegion });
12246
+ await updateProvider.delete(logicalId, currentResource.physicalId, resourceType, currentProps, { expectedRegion: this.stackRegion });
12115
12247
  } catch (deleteError) {
12116
12248
  const deleteMsg = deleteError instanceof Error ? deleteError.message : String(deleteError);
12117
12249
  if (deleteMsg.includes("does not exist") || deleteMsg.includes("not found") || deleteMsg.includes("NotFound")) this.logger.debug(`Old resource ${logicalId} already gone, proceeding with CREATE`);
12118
12250
  else throw deleteError;
12119
12251
  }
12120
- const { provider: replProvider, properties: replProps } = this.selectProviderWithSafetyNet(provider, resourceType, resolvedProps, logicalId);
12121
- const createResult = await this.withRetry(() => replProvider.create(logicalId, resourceType, replProps), logicalId, void 0, void 0, provider);
12252
+ const replDecision = this.providerRegistry.getProviderFor({
12253
+ resourceType,
12254
+ properties: resolvedProps
12255
+ });
12256
+ const replProvider = replDecision.provider;
12257
+ const replProps = replDecision.provisionedBy === "cc-api" ? this.preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) : resolvedProps;
12258
+ const createResult = await this.withRetry(() => replProvider.create(logicalId, resourceType, replProps), logicalId, void 0, void 0, replProvider);
12122
12259
  result = {
12123
12260
  physicalId: createResult.physicalId,
12124
12261
  attributes: createResult.attributes,
12125
12262
  wasReplaced: true
12126
12263
  };
12264
+ resultProvisionedBy = replDecision.provisionedBy;
12127
12265
  } else throw updateError;
12128
12266
  }
12129
12267
  if (result.wasReplaced) this.logger.info(`Resource ${logicalId} was replaced: ${currentResource.physicalId} -> ${result.physicalId}`);
@@ -12133,9 +12271,10 @@ var DeployEngine = class {
12133
12271
  properties: resolvedProps,
12134
12272
  ...result.attributes && { attributes: result.attributes },
12135
12273
  ...dependencies && dependencies.length > 0 && { dependencies },
12136
- ...this.extractTemplateAttributes(template, logicalId)
12274
+ ...this.extractTemplateAttributes(template, logicalId),
12275
+ provisionedBy: resultProvisionedBy
12137
12276
  };
12138
- this.kickOffObservedCapture(provider, logicalId, result.physicalId, resourceType, resolvedProps);
12277
+ this.kickOffObservedCapture(updateProvider, logicalId, result.physicalId, resourceType, resolvedProps);
12139
12278
  if (counts) counts.updated++;
12140
12279
  if (progress) progress.current++;
12141
12280
  const updatePrefix = progress ? `[${progress.current}/${progress.total}] ` : " ";
@@ -12145,7 +12284,7 @@ var DeployEngine = class {
12145
12284
  break;
12146
12285
  }
12147
12286
  case "DELETE": {
12148
- const currentResource = stateResources[logicalId];
12287
+ const currentResource = existingState;
12149
12288
  if (!currentResource) throw new Error(`Cannot delete ${logicalId}: resource not found in state`);
12150
12289
  const deletionPolicy = currentResource.deletionPolicy ?? template?.Resources?.[logicalId]?.DeletionPolicy;
12151
12290
  if (shouldRetainResource(deletionPolicy)) {
@@ -12153,9 +12292,13 @@ var DeployEngine = class {
12153
12292
  delete stateResources[logicalId];
12154
12293
  break;
12155
12294
  }
12295
+ const deleteProvider = this.providerRegistry.getProviderFor({
12296
+ resourceType,
12297
+ provisionedBy: currentResource.provisionedBy
12298
+ }).provider;
12156
12299
  this.logger.debug(`Deleting ${logicalId} (${resourceType})`);
12157
12300
  try {
12158
- await this.withRetry(() => provider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, { expectedRegion: this.stackRegion }), logicalId, 3, 5e3, provider);
12301
+ await this.withRetry(() => deleteProvider.delete(logicalId, currentResource.physicalId, resourceType, currentResource.properties, { expectedRegion: this.stackRegion }), logicalId, 3, 5e3, deleteProvider);
12159
12302
  } catch (deleteError) {
12160
12303
  const msg = deleteError instanceof Error ? deleteError.message : String(deleteError);
12161
12304
  if (msg.includes("does not exist") || msg.includes("was not found") || msg.includes("not found") || msg.includes("No policy found") || msg.includes("NoSuchEntity") || msg.includes("NotFoundException") || msg.includes("ResourceNotFoundException")) this.logger.debug(`Resource ${logicalId} already deleted (${msg}), removing from state`);
@@ -12279,35 +12422,23 @@ var DeployEngine = class {
12279
12422
  }
12280
12423
  }
12281
12424
  /**
12282
- * Select the appropriate provider for create/update, falling back to CC API
12283
- * if the SDK provider doesn't handle all template properties.
12425
+ * Prepare a property map for a Cloud Control API call. When a Tier 1
12426
+ * resource is routed via Cloud Control (either because the user's
12427
+ * template hit silent-drop properties under #614 or because the resource
12428
+ * is sticky-routed via `provisionedBy: 'cc-api'`), CC requires the full
12429
+ * property map — including identifier-like fields (`BucketName`,
12430
+ * `RoleName`, etc.) that the SDK provider would have auto-generated.
12431
+ * This helper threads the property prep through the registered SDK
12432
+ * provider's `preparePropertiesForFallback` hook when defined, falling
12433
+ * back to `applyDefaultNameForFallback` (which mints stack-prefixed
12434
+ * names matching what the SDK provider would have done) otherwise.
12284
12435
  *
12285
- * This safety net prevents properties from being silently dropped when an SDK
12286
- * provider only maps a subset of CloudFormation properties.
12287
- *
12288
- * DELETE always uses the SDK provider (force-delete, cleanup, etc.).
12436
+ * No-ops for types with no registered SDK provider (Tier 2 / CC-native).
12289
12437
  */
12290
- selectProviderWithSafetyNet(sdkProvider, resourceType, resolvedProps, logicalId) {
12291
- const handledSet = sdkProvider.handledProperties?.get(resourceType);
12292
- if (!handledSet) return {
12293
- provider: sdkProvider,
12294
- properties: resolvedProps
12295
- };
12296
- const unhandledProps = Object.keys(resolvedProps).filter((p) => !handledSet.has(p));
12297
- if (unhandledProps.length === 0) return {
12298
- provider: sdkProvider,
12299
- properties: resolvedProps
12300
- };
12301
- if (CloudControlProvider.isSupportedResourceType(resourceType) && !sdkProvider.disableCcApiFallback) {
12302
- this.logger.info(`${logicalId}: SDK provider does not handle [${unhandledProps.join(", ")}] — falling back to CC API for create/update`);
12303
- const fallbackProps = sdkProvider.preparePropertiesForFallback ? sdkProvider.preparePropertiesForFallback(logicalId, resourceType, resolvedProps) : applyDefaultNameForFallback(logicalId, resourceType, resolvedProps);
12304
- return {
12305
- provider: this.providerRegistry.getCloudControlProvider(),
12306
- properties: fallbackProps
12307
- };
12308
- }
12309
- const reason = sdkProvider.disableCcApiFallback ? "CC API fallback is disabled for this provider (known CC API issues)" : `CC API does not support ${resourceType}`;
12310
- throw new ProvisioningError(`SDK provider for ${resourceType} does not handle properties [${unhandledProps.join(", ")}] and ${reason}. These properties would be silently dropped. Please update the SDK provider to handle all required properties.`, resourceType, logicalId, "");
12438
+ preparePropertiesForCcApi(resourceType, resolvedProps, logicalId) {
12439
+ const sdkProvider = this.providerRegistry.getRegisteredTypes().includes(resourceType) ? this.providerRegistry.getProvider(resourceType) : void 0;
12440
+ if (sdkProvider?.preparePropertiesForFallback) return sdkProvider.preparePropertiesForFallback(logicalId, resourceType, resolvedProps);
12441
+ return applyDefaultNameForFallback(logicalId, resourceType, resolvedProps);
12311
12442
  }
12312
12443
  /**
12313
12444
  * Execute an operation with retry for transient IAM propagation errors.
@@ -12373,4 +12504,4 @@ var DeployEngine = class {
12373
12504
 
12374
12505
  //#endregion
12375
12506
  export { CdkdError as $, shouldRetainResource as A, resolveSkipPrefix as B, IntrinsicFunctionResolver as C, TemplateParser as D, DagBuilder as E, Synthesizer as F, CFN_TEMPLATE_URL_LIMIT as G, resolveStateBucketWithDefaultAndSource as H, getDefaultStateBucketName as I, uploadCfnTemplate as J, MIGRATE_TMP_PREFIX as K, getLegacyStateBucketName as L, stringifyValue as M, WorkGraph as N, LockManager as O, buildDockerImage as P, AssetError as Q, resolveApp as R, assertRegionMatch as S, DiffCalculator as T, warnDeprecatedNoPrefixCliFlag as U, resolveStateBucketWithDefault as V, CFN_TEMPLATE_BODY_LIMIT as W, clearBucketRegionCache as X, AssemblyReader as Y, resolveBucketRegion as Z, matchesCdkPath as _, formatError as _t, withRetry as a, LockError as at, ProviderRegistry as b, withErrorHandling as bt, bold as c, PartialFailureError as ct, green as d, ResourceUpdateNotSupportedError as dt, ConfigError as et, red as f, RouteDiscoveryError as ft, CDK_PATH_TAG as g, SynthesisError as gt, collectInlinePolicyNamesManagedBySiblings as h, StateError as ht, withResourceDeadline as i, LocalStartServiceError as it, AssetPublisher as j, S3StateBackend as k, cyan as l, ProvisioningError as lt, IAMRoleProvider as m, StackTerminationProtectionError as mt, DEFAULT_RESOURCE_WARN_AFTER_MS as n, LocalInvokeBuildError as nt, IMPLICIT_DELETE_DEPENDENCIES as o, MissingCdkCliError as ot, yellow as p, StackHasActiveImportsError as pt, findLargeInlineResources as q, DeployEngine as r, LocalMigrateError as rt, formatResourceLine as s, NestedStackChildDirectDestroyError as st, DEFAULT_RESOURCE_TIMEOUT_MS as t, DependencyError as tt, gray as u, ResourceTimeoutError as ut, normalizeAwsTagsToCfn as v, isCdkdError as vt, applyRoleArnIfSet as w, CloudControlProvider as x, resolveExplicitPhysicalId as y, normalizeAwsError as yt, resolveCaptureObservedState as z };
12376
- //# sourceMappingURL=deploy-engine-BzrECC3i.js.map
12507
+ //# sourceMappingURL=deploy-engine-BXWv-yRb.js.map