--allow-unsupported-types (deploy + destroy)
cdkd rejects genuinely-unsupported resource types at pre-flight —
before any resource is touched — instead of letting them fail mid-deploy
with an opaque Cloud Control error. A type is "unsupported" when AWS
reports it as ProvisioningType: NON_PROVISIONABLE (the provider-coverage
Tier 3 set: Cloud Control API cannot create/update/delete it) AND cdkd
has no SDK provider for it. The Tier 3 set is generated from the audit
cache into the runtime at src/provisioning/unsupported-types.generated.ts
(vp run gen:unsupported-types; CI fails if it drifts).
When pre-flight hits one, the error names each type, the reason, a 1-click pre-filled GitHub issue link to request support, and the exact re-run command:
The following resource types are not supported by cdkd:
- AWS::AppMesh::Mesh
AWS reports this type as NON_PROVISIONABLE (Cloud Control API cannot
manage it) and cdkd has no SDK provider for it.
Request support: https://github.com/go-to-k/cdkd/issues/new?title=...
To attempt deployment anyway (Cloud Control will likely fail for
NON_PROVISIONABLE types), re-run with: --allow-unsupported-types AWS::AppMesh::Mesh
--allow-unsupported-types <types> is the escape hatch: a
comma-separated (and repeatable) list of types to attempt via Cloud
Control anyway. It is per-type rather than a blanket override so you
explicitly acknowledge each type. Useful mainly for a type the cached
audit marks Tier 3 that AWS has since made provisionable (regenerate the
audit with vp run audit:coverage:regenerate for the permanent fix). It
is available on both cdkd deploy and cdkd destroy (and cdkd state destroy) so a stack deployed with the flag can also be torn down.
cdkd deploy MyStack --allow-unsupported-types AWS::AppMesh::Mesh,AWS::Budgets::Budget
cdkd destroy MyStack --allow-unsupported-types AWS::AppMesh::Mesh,AWS::Budgets::Budget
--allow-unsupported-properties (deploy)
When a CDK template uses a top-level CFn property that cdkd's SDK
provider would silently drop on write (e.g. AWS adds CapacityProviderConfig
to AWS::Lambda::Function, CDK adds support, you write it in your CDK code,
but LambdaFunctionProvider.create() does not read it yet), cdkd auto-routes
the resource through Cloud Control API by default. Cloud
Control forwards the full property map to AWS verbatim, so the silent
drop is closed without any user intervention — the field reaches AWS.
The routing decision is recorded on the resource's state record as
provisionedBy: 'cc-api' and stays sticky for the resource's lifetime
(cdkd drift, cdkd destroy, etc. route through the same layer that
created it — even if cdkd later adds first-class SDK provider support
for the property). cdkd state show <stack> displays the
ProvisionedBy: field so you can audit which layer owns each resource.
The set of handled vs silently-dropped properties is generated from the
CFn schema fixtures + each SDK provider's handledProperties /
unhandledByDesign declarations into the runtime at
src/provisioning/property-coverage.generated.ts (vp run gen:property-coverage;
CI fails if it drifts). Coverage is per Tier 1 (SDK provider) type only —
Tier 2 (Cloud Control fallback) types already forward the full property
map to AWS, so the auto-route is a no-op for them.
When the auto-route fires, cdkd logs an info line per affected resource:
[info] MyLambda (AWS::Lambda::Function): routing via Cloud Control API
(cdkd's SDK Provider does not yet wire CapacityProviderConfig — CC API
will forward the full property map. Override via
--allow-unsupported-properties AWS::Lambda::Function:CapacityProviderConfig.)
--allow-unsupported-properties <entries> (override)
The flag is the opt-out from the default CC auto-route. Each entry
is a <ResourceType>:<PropertyName> token (comma-separated and
repeatable); the flag pins the resource to the SDK provider path and
accepts the silent drop for the named property. A warn line is
logged so the silent drop is auditable.
cdkd deploy MyStack --allow-unsupported-properties AWS::Lambda::Function:CapacityProviderConfig,AWS::Lambda::Function:FunctionScalingConfig
Per type+property pair (not blanket) so you explicitly acknowledge each
silent drop. The flag is deploy-only — destroy uses the per-resource
physical ID and the state-recorded provisionedBy layer, not the
template properties.
Properties that do not appear in the CFn schema pass through silently —
these are usually addPropertyOverride escape hatches or typos, both of
which CFn itself tolerates. Read-only properties (AWS-managed Arns, Ids,
etc.) also pass through silently; you cannot set them from the template
side and they are no-ops if they appear there.
When to use the override flag (auto-route opt-out guidance)
The auto-route is the default because silent drop is a real bug class (the deployed resource is missing fields the user wrote). Cloud Control closes the bug by forwarding the full property map. The flag is the opt-out for situations where you specifically want the SDK provider path even at the cost of the silent drop.
Use the flag when
- You need the SDK provider's fast synchronous-AWS-call path and the dropped property is non-essential for your use case (e.g. a structural CDK construct emits a property you do not care about).
- A Cloud Control side-effect bothers you — e.g. CC names a resource differently than cdkd's SDK provider would have, and you want the SDK naming convention to win.
- You have an existing SDK-managed resource (
provisionedBy: 'sdk') that you want to keep on the SDK path even after a new property appears in the template. Without the flag, the next deploy would auto-route it through CC (the routing decision re-evaluates per deploy — only a resource whose state already says'cc-api'is sticky to CC; a still-SDK resource with new silent-drop properties re-routes).
Do NOT use the flag when
- The dropped property is security-meaningful — e.g.
KmsKeyArn,MonitoringRoleArn,MasterUserSecret, IAM policy attachments, resource-policy fields, encryption settings, TLS configuration. Silent drop here is a real-world incident. Without the flag, the auto-route closes the silent drop by sending the property to AWS via CC; with the flag, you opt back into the silent drop. - You are prototyping and don't care about routing — the default auto-route already gets the property to AWS.
Decision summary
| Situation | Recommended action |
|---|---|
| Fresh deploy, template uses a silent-drop property | Default auto-route via Cloud Control (no flag needed) |
| Existing CC-managed resource, want to stay on CC | Default routing (sticky) — no flag needed |
| Existing SDK-managed resource, new silent-drop property appears | Default re-routes through CC. To stay on SDK, use --allow-unsupported-properties |
| You explicitly want SDK semantics + accept the silent drop | This flag |
| Property is security-meaningful | Do not use the flag — let the CC auto-route close the silent drop |
What the flag is NOT
- NOT a request to cdkd to start handling the property. The provider is unchanged; with the flag, the property is silently dropped at write time. (Without the flag, the resource takes the CC route and the property reaches AWS verbatim.)
- NOT persisted in cdkd state. Every deploy must pass the flag if
the override is still desired; the resource's
provisionedBystate field reflects the routing actually used at last deploy.
cdkd is currently a dev/test tool (see "Important Notes" in CLAUDE.md); the CC auto-route closes a long-standing silent-drop bug class by default. For production workloads, use the AWS CDK CLI until cdkd's property coverage matches your needs.
--recreate-via-cc-api <LogicalId> + --force-stateful-recreation (deploy)
--recreate-via-cc-api <LogicalId> (repeatable, one flag per resource)
destroys + recreates the named resource via Cloud Control API in this
deploy, so a previously-silent-dropped top-level CFn property reaches
AWS on the recreated copy. This is the mid-life counterpart to the
default-on auto-route for fresh deploys.
When to use it:
- An existing resource is
provisionedBy: 'sdk'in cdkd state, and you want to start using a top-level CFn property cdkd's SDK provider does not yet wire (e.g. addingCapacityProviderConfigto an already-deployed Lambda). Adding the property on the next deploy alone won't reach AWS — the SDK update path drops it silently. The flag forces a destroy + recreate cycle so the new physical resource lands on CC and the property reaches AWS.
When NOT to use it:
- The resource is already
provisionedBy: 'cc-api'(sticky). The update path already routes via CC; the recreate is a no-op. cdkd refuses pre-flight withblockedAlreadyCcApi— the destroy + recreate cycle would produce identical end state at the cost of unnecessary downtime. Mirror of theblockedAlreadySdkrefusal on the reverse direction. Fix: drop the flag for that resource. - Fresh deploy (the resource is not yet in cdkd state). The default auto-route handles fresh silent-drop deploys automatically — no flag needed.
# Recreate a single Lambda (stateless, no extra flag needed)
cdkd deploy MyStack --recreate-via-cc-api MyLambda --yes
# Recreate two Lambdas in one deploy (repeat the flag — comma-split is intentionally unsupported)
cdkd deploy MyStack \
--recreate-via-cc-api MyLambda \
--recreate-via-cc-api OtherFn \
--yes
# Recreate a stateful resource — TWO flags required + data loss is acknowledged
cdkd deploy MyStack \
--recreate-via-cc-api MyTable \
--force-stateful-recreation \
--yes
Stateful-resource guard
The flag refuses to operate on resource types that carry user data
without --force-stateful-recreation. Two-flag protection mirrors the
--remove-protection pattern.
Guard list (always stateful — destroy loses ALL data, no automatic migration):
| Category | Types |
|---|---|
| Database / storage | AWS::RDS::DBInstance, AWS::RDS::DBCluster, AWS::DocDB::DBInstance, AWS::DocDB::DBCluster, AWS::Neptune::DBInstance, AWS::Neptune::DBCluster, AWS::DynamoDB::Table, AWS::DynamoDB::GlobalTable |
| Filesystem / blob | AWS::EFS::FileSystem, AWS::FSx::FileSystem, AWS::ECR::Repository, AWS::EC2::Volume |
| Streaming | AWS::Kinesis::Stream |
| Search | AWS::Elasticsearch::Domain, AWS::OpenSearchService::Domain |
| Identity / config | AWS::Cognito::UserPool, AWS::SecretsManager::Secret, AWS::SSM::Parameter |
| Metadata catalog | AWS::Glue::Database, AWS::Glue::Table |
| Edge | AWS::CloudFront::Distribution (URL changes break consumers; ~20-minute propagation) |
Conditionally stateful (guard fires only when the resource actually contains data):
AWS::S3::Bucket— guard fires when the bucket has at least one current version, prior version, or delete-marker. cdkd issues a single-pages3:ListObjectVersions(MaxKeys=1)against each S3 bucket target at plan time; empty buckets pass through, non-empty buckets (including versioned buckets whose current keys are soft-deleted but whose history is still retained) are refused unless--force-stateful-recreationis supplied. cdkd usesListObjectVersionsrather thanListObjectsV2so the probe's view of "empty" matches what the destroy + recreate cycle would actually wipe. If the probe itself fails (permission denied, transient network error), cdkd logs a warn and falls through to the conservative "not stateful" sync result — pass--force-stateful-recreationto proceed when the bucket might hold data and the probe could not be verified.AWS::Logs::LogGroup— guard fires whenRetentionInDays > 0on the recorded state. Log groups without retention configured are treated as ephemeral.
There is no per-resource granularity on --force-stateful-recreation
— when set, EVERY named recreate target bypasses the stateful guard.
The user is opting into a footgun; per-resource force would imply a
false sense of granularity.
Interactive confirmation
cdkd deploy --recreate-via-cc-api <id> prints a per-target plan
(logical id + resource type + stateful reason where applicable) and
then asks Continue? (y/N) before any AWS call. Default is N
(destructive — the destroy + recreate cycle is irreversible per
resource). Combine with --yes / -y for non-interactive CI runs;
the plan is then warn-logged once and the deploy proceeds without
prompting. Non-TTY runs without --yes are rejected with an
actionable error rather than hanging on a closed stdin.
For stateful targets (those reaching pre-flight only because the user
opted in with --force-stateful-recreation), the prompt prefixes each
row with **DATA LOSS** and emits an explicit DATA: all data in <logical id> will be lost caveat — the third "stop and think" moment
on top of the two-flag opt-in.
Cross-stack reference propagation
The recreated resource gets a fresh physical id. Downstream stacks that
read this resource's outputs via Fn::GetStackOutput /
Fn::ImportValue must be re-deployed before they see the new id. A
warn line lists this caveat at recreate time; cdkd does NOT walk the
state bucket to enumerate downstream consumers in v1 (deferred to a
follow-up issue). Plan multi-stack recreates from leaf to root.
Interaction with --allow-unsupported-properties
--recreate-via-cc-api MyLambda combined with
--allow-unsupported-properties AWS::Lambda::Function:CapacityProviderConfig
on a resource whose template carries CapacityProviderConfig is ambiguous
intent:
- Does the user want SDK + silent drop (override path)?
- Does the user want CC migration (recreate path)?
cdkd refuses with a pre-flight error naming the overlap. Pick one strategy per resource.
Reversibility (one-way at v1)
Once a resource is provisionedBy: 'cc-api', going back to the SDK
Provider requires another flag (the inverse --recreate-via-sdk). NOT
in scope for v1 — file an issue if you need this direction.
When a later cdkd release wires the property the user originally needed, the migrated resource stays on CC unless the user explicitly switches it back. Sticky-state semantics avoid SDK↔CC ping-pong on every backfill release.
What --recreate-via-cc-api is NOT
- NOT a per-stack shortcut. There is no
--recreate-via-cc-api-all-with-silent-dropsform — the user names each target explicitly to acknowledge the cost. - NOT persisted in cdkd state. The next deploy WITHOUT the flag routes the recreated resource via CC (sticky); the flag is only needed to trigger the initial destroy + recreate.
- NOT compatible with cross-account / cross-region migration — the flag operates within the current deploy's environment only.
- NOT compatible with Tier 3 (
NON_PROVISIONABLE) types — CC API can't handle them either; the existing Tier 3 reject fires first. - NOT compatible with multi-region resources like
AWS::DynamoDB::GlobalTablein v1 — the destroy + recreate cycle across replica regions is more involved; cdkd refuses with a clear error.
--replace (deploy)
Replace (DELETE + CREATE) a resource whose in-place update is rejected
because an immutable property changed and AWS exposes no update API for
it. Some resource types are immutable on AWS — there is no
Update<Thing> call, so any property change must publish / register a
new physical resource. Examples: AWS::Lambda::LayerVersion content,
AWS::EFS::AccessPoint, AWS::ECS::TaskDefinition,
AWS::Glue::SecurityConfiguration, and several AWS::ApiGatewayV2::*
identity fields.
For a few of these cdkd already has a built-in replacement rule (e.g.
AWS::Lambda::LayerVersion auto-replaces with no flag). For the rest,
cdkd's diff classifies the change as an in-place UPDATE, the provider's
update() hard-rejects with ResourceUpdateNotSupportedError, and —
without this flag — the deploy fails. --replace opts into catching that
rejection and falling back to a DELETE + CREATE of the resource (the same
replacement path the Cloud Control UnsupportedActionException
auto-fallback already uses), matching what CloudFormation would do.
Unlike --recreate-via-cc-api / --recreate-via-sdk-provider (which
name a specific logical id and force a routing migration), --replace is
a stack-wide opt-in that fires only for resources whose update genuinely
hard-rejects — a resource whose update succeeds in place is unaffected.
# A Glue SecurityConfiguration's EncryptionConfiguration changed (immutable) —
# fails without the flag, replaces cleanly with it
cdkd deploy MyStack --replace --yes
Stateful-resource guard (shared with --recreate-via-cc-api)
--replace shares the same stateful-resource guard as
--recreate-via-cc-api: when the replacement target is a stateful type
(see the guard list above — RDS / DynamoDB / EFS / S3-with-data /
Logs-with-retention / etc.), the DELETE + CREATE loses all data, so
cdkd refuses unless --force-stateful-recreation is ALSO passed. The
guard is evaluated at the moment the immutable-update rejection is
caught (mid-deploy), and the error names the resource + the data-loss
reason. Non-stateful immutable types (LayerVersion, Glue
SecurityConfiguration, ECS TaskDefinition, ApiGatewayV2 sub-resources,
etc.) replace with --replace alone.
The same stateful guard ALSO covers property-driven replacement — a
replacement cdkd detects directly from the diff (an immutable / createOnly
property changed in the template, e.g. AWS::EFS::FileSystem.PerformanceMode,
an AWS::EC2::Volume AvailabilityZone move, or an S3 BucketName rename)
rather than from a provider's mid-deploy update rejection. A plain cdkd deploy (no --replace flag) that would DELETE+CREATE
a stateful resource because of such a change now requires
--force-stateful-recreation and throws STATEFUL_REPLACE_BLOCKED without it,
closing the prior footgun where a template immutable-property change silently
destroyed a stateful resource's data. Non-stateful types still replace freely
on cdkd deploy with no flag.
--recreate-via-sdk-provider <LogicalId> (deploy)
--recreate-via-sdk-provider <LogicalId> (repeatable, one flag per
resource) is the reverse direction of --recreate-via-cc-api.
It destroys + recreates the named resource via cdkd's
SDK Provider so a resource currently sticky on provisionedBy: 'cc-api'
flips back to provisionedBy: 'sdk'.
When to use it:
- A
provisionedBy: 'cc-api'-sticky resource (landed on CC because the user originally needed a top-level CFn property cdkd's SDK Provider did not wire, e.g. LambdaLoggingConfig) is now eligible for SDK Provider routing because a later cdkd release has added SDK coverage for that property. The flag forces a destroy + recreate cycle so the new physical resource lands on SDK and benefits from SDK Provider performance / diagnostic clarity / narrower IAM scope. - A
provisionedBy: 'cc-api'resource where the user no longer needs the CC route (e.g. removed the silent-drop property from the template) and wants to consolidate routing back to SDK for the reasons above.
When NOT to use it:
- The resource is already
provisionedBy: 'sdk'(or pre-v7 legacy state, treated as SDK by the v7 binary) — the reverse migration is a no-op. cdkd refuses with a clear error. - The resource type has no SDK provider registered (Tier 2 CC-only) — the destroy + recreate would route via CC again. cdkd refuses.
- The template still uses a silent-drop property NOT in
--allow-unsupported-properties— the default-on CC auto-route would re-route the SDK-recreated resource back to CC on the very next routing decision. cdkd refuses (inverse ambiguous intent); fix by either removing the property from the template or accepting the silent drop via--allow-unsupported-properties <Type>:<Prop>.
Stateful-resource guard, multi-region refusal, and the interactive
Continue? (y/N) prompt are symmetric to --recreate-via-cc-api:
named stateful targets (RDS / DynamoDB / S3-with-data / etc.) refuse
unless --force-stateful-recreation is also passed, multi-region
resources (AWS::DynamoDB::GlobalTable) refuse outright in v1, and
the prompt fires per-stack with the same **DATA LOSS** prefix on
stateful entries. The two flags are mutually exclusive on a per-resource
basis — naming the same logical id in both is refused as ambiguous.
# Mid-life CC→SDK migration after a backfill release landed SDK coverage
# for Lambda's LoggingConfig:
cdkd deploy MyStack --recreate-via-sdk-provider MyLambda --yes
# Multiple targets:
cdkd deploy MyStack \
--recreate-via-sdk-provider MyLambda \
--recreate-via-sdk-provider OtherFn \
--yes
What --recreate-via-sdk-provider is NOT
- NOT a per-stack shortcut. Per-resource explicit naming only.
- NOT the only path to SDK routing — fresh CREATEs route via the
routing-decision matrix in
ProviderRegistry.getProviderForand land on SDK whenever an SDK Provider is registered for the type AND the template has no silent-drop property. This flag is for the existing-state CC → SDK migration only. - NOT compatible with
--recreate-via-cc-apion the same logical id — pick ONE direction per resource.
--strict-getatt (deploy)
Fail the deploy on ANY Fn::GetAtt that falls back to the resource's
physical ID because cdkd cannot construct the requested attribute, and on
any stack Output that cannot be resolved.
cdkd deploy MyStack --strict-getatt
Default behavior (without the flag)
When a template requests an attribute that is neither captured in state
attributes nor constructible by the resolver's per-type mappings, cdkd
falls back to the resource's physical ID:
Knowably-wrong shapes hard-fail even without the flag: an attribute name ending in
Arnwhose fallback value is notarn:-shaped, or ending inUrlwhose fallback is not an http(s) URL, cannot be what CloudFormation would return — the deploy fails with an actionable error naming the resource, attribute, and an issue link. This applies to the resolver's final unknown-type fallback AND to every per-type handler's unknown-attribute default branch.Every other suffix warns and returns the physical ID (
Unknown attribute X for resource type Y, returning physical ID) — an alias or endpoint is shape-indistinguishable from a plain name, so a hard-fail there would risk failing correct deploys.The same refusals apply inside
Fn::Sub. A${LogicalId.Attribute}placeholder resolves through the same code path, so a reference that hard-fails as a resource property hard-fails there too. PreviouslyFn::Subdowngraded EVERY resolution failure to a warning and kept the raw${...}text, so the identical reference shipped a literal${MyResource.SomeArn}to AWS on a green deploy. A variable that genuinely does not exist still warns and keeps its placeholder — the long-standing behavior — and the warning now names the actual reason instead of always sayingnot found.When at least one such fallback happened, the deploy summary prints a one-line count so the warns don't scroll away on green deploys:
2 attribute resolution(s) fell back to the physical ID (potentially wrong values); re-run with --strict-getatt to fail on theseEach distinct fallback site is counted once per run (diff-phase resolutions are not double-counted against provisioning-phase ones). The count is per stack: a nested-stack child's fallbacks are counted by the child's own deploy engine and are not aggregated into the parent's summary line.
An Output whose value cannot be resolved is warned about and skipped (no value is persisted or exported); the deploy still exits 0.
With --strict-getatt
- EVERY unknown-attribute physical-ID fallback — any suffix, including an
ARN-shaped fallback for an
*Arnattribute — is a hard error. - An Output resolution failure fails the deploy instead of silently
publishing nothing (which would otherwise break downstream
Fn::ImportValueconsumers with "export not found" long after this deploy exited 0). The failure fires AFTER all resource operations succeeded, so cdkd persists the provisioning result to state BEFORE failing: the created/updated resources are recorded (previously persisted outputs are kept), no rollback runs, and a follow-upcdkd deployorcdkd destroysees them — even on a first deploy, nothing becomes an invisible orphan.
Use it in CI to guarantee no potentially-wrong Fn::GetAtt value ever
ships quietly; drop it (default) when a known-benign fallback (e.g. a
physical ID that genuinely is the attribute value for a type cdkd has not
enriched yet) is acceptable. Nested-stack child deploys inherit the flag
from the parent deploy.
--allow-unaddressed (deploy)
A deploy that finishes without a single resource FAILING can still leave a
resource cdkd was responsible for alive in AWS. That outcome exits 2,
matching what cdkd destroy does for the identical
case. Two cases produce it, and
they differ in whether they heal themselves:
| Summary row | Cause | Next cdkd deploy retries it? |
|---|---|---|
Skipped (not deleted): N |
a resource removed from the template whose provider could not issue the delete — typically a malformed physicalId in state |
Yes. The state record is deliberately KEPT, so the resource is still diffed as a DELETE next run |
of which left an orphaned predecessor: N |
a replacement whose new resource was created and whose OLD one could not be deleted | No. State now points at the replacement, so the survivor is untracked — delete it by hand |
cdkd deploy MyStack # exit 2 if either row is non-zero
cdkd deploy MyStack --allow-unaddressed # exit 0 for the same run
The flag changes the exit code, and with it the run-level error message.
What survives it: the summary rows, each resource's own warning (naming its
cause and remedy), the switched banner, the skipped figure in cdkd events,
and the RUN_FINISHED result: 'FAILED' record. So a run that used the flag
still says — in its log and in its durable post-mortem — that a resource
survived. The events store records what happened, not what the operator chose
to tolerate.
What it DOES take away, beyond the exit code: the PartialFailureError message
is not raised at all, and that message is the only place the run-level
remediation text appears ("a skipped DELETE keeps its state record… a
replacement's survivor is not tracked — delete it by hand") along with the count
of stacks that were cancelled and never deployed. The banner's closing sentence
also differs. If you set the flag in CI, the per-resource warnings remain your
route to the cause.
It exists because the orphaned-predecessor case has a legitimate
not-yet-fixable window. The commonest instance is an ACM certificate
replacement rejected because a consumer — often a CloudFront distribution in
another stack not yet updated — still references the old certificate; the
delete succeeds on its own once DescribeCertificate.InUseBy is empty. Until
then a pipeline would be red for a cause it cannot act on.
Prefer the flag over wrapping the command in a shell exit-code test. cdkd deploy also exits 2 for MacroExpansionError (a synth-time macro failure)
and ResourceUpdateNotSupportedError, so cdkd deploy || [ $? -eq 2 ] would
silence those unrelated real failures; --allow-unaddressed is scoped to this
one cause.
--no-cfn-fallback (deploy / diff)
By default, a cross-stack
reference that is not found in cdkd state falls back to CloudFormation,
so a cdkd-deployed consumer can reference a producer stack still managed
by CloudFormation (cdk deploy / raw CFn):
Fn::ImportValue→ CloudFormationListExportsin the consumer's region (CFn's own semantic for the intrinsic).Fn::GetStackOutput→ CloudFormationDescribeStacksoutputs in the target region. Same-account only — theRoleArn(cross-account) form never takes the fallback.
The fallback fires ONLY after a cdkd-state miss (cdkd-first precedence:
existing cdkd-to-cdkd references are untouched, and a name collision
resolves to the cdkd export). A CFn-sourced resolution is a weak
reference — not recorded into state.imports / state.outputReads,
so there is no destroy-time protection in either direction: deleting the
CFn producer breaks the consumer's next resolve, not the producer's
delete. A fallback lookup failure (e.g. missing
cloudformation:ListExports / cloudformation:DescribeStacks
permission) logs a warning and surfaces the original not-found error.
cdkd deploy MyStack --no-cfn-fallback # cdkd-state-only resolution
cdkd diff MyStack --no-cfn-fallback # preview with the same semantics
Pass the flag when you want cdkd-state-only semantics — IAM kept minimal,
or an export-name typo failing fast instead of accidentally matching an
unrelated CloudFormation export in the account. Nested-stack child
deploys inherit the flag from the parent deploy; cdkd diff honors it in
its best-effort resolvers so preview and apply resolve identically. See
docs/cross-stack-references.md for the full
design.