Skip to content

Tracking: cdktn configuration & identity surface #225

@so0k

Description

@so0k

Tracking: cdktn Configuration & Identity Surface

Purpose: Identify the cdktf-named surface areas, the tensions and user-impact each carries, and the known mitigation patterns. This issue does not decide any of these items - each area is its own design problem with its own discussion or follow-up issue.
Time horizon: Multi-release, gradual. Not scoped to a single release.

Background

v0.22 (the first cdktn release) intentionally preserved the entire cdktf-named configuration and identity surface to keep the upgrade cost near zero. The cdktn CLI today reads cdktf.json, writes to cdktf.out/, honors CDKTF_* env vars, caches under ~/.cdktf, emits a cdktfVersion context key on synthesized stacks, and marks class instances with Symbol.for("cdktf/*") keys. That commitment is explicit in upgrade-guide-v0-22:

The cdktn cli should be fully compatible with existing CDK for Terraform projects for at least v0.22.

The rename achieved minimal friction — friction was low enough that no cdktn migrate command was created.

Since then the project has stabilized (CVEs cleared, EOL runtimes updated, unmaintained deps replaced) and contributor velocity is up. Incoming opportunities is expected to bring in new users. The friction is now inverted: the split identity is creating real bugs and unclear guidance for new contributors:

  • cdktn-io/cdktn-provider-project deprecated the cdktf package.json metadata key in favor of cdktn, and the CLI's prebuilt-providers.ts:219 reads cdktn-first (packageJson.cdktn?.provider ?? packageJson.cdktf?.provider). One surface is cdktn-first.
  • The rest of the surface is still cdktf-only with no cdktn alternative.
  • New users see cdktn synth write to cdktf.out/ with a cdktfVersion metadata key. New contributors adding env vars or config keys have no clear rule for which name to use.
  • Unclear guidance is producing real bugs in downstream tooling.

This issue inventories the surface so it can be addressed area-by-area across multiple releases.

Scope

In scope: identifying and tracking the cdktf-named surface areas that will need attention as the project moves toward a fully cdktn identity, gradually, across multiple releases. Highlighting tensions and user-impact for each.

Out of scope: Terraform-vs-OpenTofu behavioral divergence — selection of the underlying binary already works today via the TERRAFORM_BINARY_NAME env var (packages/cdktn/src/util.ts:10, packages/@cdktn/commons/src/terraform.ts:6, packages/@cdktn/provider-schema/src/provider-schema.ts:24). The unsolved problem is that terraform and tofu don't behave identically — the known provider codegen failure when TERRAFORM_BINARY_NAME=tofu is one manifestation. That belongs in its own tracking issue, not here.

Deciding the specific implementation for any one area below should be done by RFC(s) tracked against this parent issue

Preferred mitigation pattern: feature flags

Where surface changes affect serialized state, deployed stacks, or cross-package runtime behavior, feature flags (the existing FUTURE_FLAGS mechanism in packages/cdktn/src/features.ts) should be the preferred mitigation. They let new projects (bootstrapped via cdktn init) opt into the cdktn-named surface by default while letting existing projects keep current behavior until they explicitly opt in.

For pure read paths with no state implications (config-file lookup, env var reads, output dir defaults), a simple cdktn-first/cdktf-fallback may be sufficient without a flag. The reference implementation already exists at packages/@cdktn/cli-core/src/lib/dependencies/prebuilt-providers.ts:219.

Problem areas

Each area below is described with its current state, the tension it carries, the user-impact of changing it, and any known mitigation. No area is being decided here.

1. Config file: cdktf.json

  • Current: packages/@cdktn/commons/src/config.ts:31const CONFIG_FILE = "cdktf.json". Only this name is read.
  • Tension: New users expect cdktn.json. Existing users have cdktf.json checked into thousands of projects.
  • User impact of a change: Adding cdktn.json as a primary lookup with cdktf.json fallback is low risk (pure read path). Removing cdktf.json support is high risk.
  • PR(s): feat(cli): support cdktn.json config file #153

2. Output directory: cdktf.out/

  • Current: packages/@cdktn/commons/src/config.ts:56 (CONFIG_DEFAULTS.output = "cdktf.out") and packages/cdktn/src/app.ts:101 (this.outdir = config.outdir ?? process.env.CDKTF_OUTDIR ?? "cdktf.out").
  • Tension: New users expect cdktn.out/. Existing CI/CD pipelines, .gitignore files, deploy scripts, and IDE configs hard-code cdktf.out/.
  • User impact of a change: Flipping the default is potentially breaking for any pipeline that assumes the path. Decoupling the change from the config-file change vs. coupling them is itself an open question.

3. Environment variables: CDKTF_*

Eight variables, no CDKTN_* aliases:

Variable Purpose
CDKTF_OUTDIR Output dir override
CDKTF_TARGET_STACK_ID Single-stack synthesis filter
CDKTF_CONTEXT_JSON Context values as JSON
CDKTF_LOG_LEVEL Log verbosity
CDKTF_LOG_FILE_DIRECTORY Log file output path
CDKTF_HOME Cache/config home
CDKTF_DISABLE_PLUGIN_CACHE_ENV Disable plugin cache
CDKTF_CONTINUE_SYNTH_ON_ERROR_ANNOTATIONS Don't fail on annotations
CDKTF_EXPERIMENTAL_PROVIDER_SCHEMA_CACHE_PATH Provider schema cache
  • Tension: Users set these in shell profiles, .envrc, CI secrets. New contributors don't know whether to add CDKTF_* or CDKTN_* for a new var. See feat(lib): allow disabling creation stacks #215
  • User impact of a change: Pure read paths; dual-read is low risk. Removing legacy is shell-profile-breaking.

4. Cache / home directory: ~/.cdktf

  • Current: packages/@cdktn/commons/src/checkpoint.ts:19 — hardcoded ~/.cdktf (overridable via CDKTN_HOME / CDKTF_HOME).
  • Tension: Cached provider schemas, project-tracking IDs, telemetry data live there. Moving it leaves stale data behind.
  • User impact of a change: Either silently migrate cached data, leave both, or accept duplication. All have trade-offs.

5. Synthesized stack metadata: cdktfVersion context key

  • Current: packages/cdktn/src/app.ts:120 sets node.setContext("cdktfVersion", version). packages/cdktn/src/terraform-stack.ts:88,220,350 reads it and serializes version: this.cdktfVersion into the synthesized output.
  • Tension: This value lands in cdk.tf.json. Terraform itself ignores it, but downstream tooling and snapshot tests may read it.
  • User impact of a change: Renaming the key changes synthesized output (snapshot-test diffs). Aliasing (emit both, read either) avoids that.

6. Provider metadata key in package.jsonreference example, already cdktn-first

  • Current: packages/@cdktn/cli-core/src/lib/dependencies/prebuilt-providers.ts:219packageJson.cdktn?.provider ?? packageJson.cdktf?.provider. Upstream cdktn-io/cdktn-provider-project has deprecated the cdktf key.
  • Tension: None — this is the reference pattern. Listed here to make it discoverable and document it as the canonical example for other surface changes.

7. Logger appender name: "cdktf"

  • Current: packages/@cdktn/commons/src/logging.ts:83 — log4js appender literally named "cdktf".
  • Tension: Internal-only; not user-visible unless someone configures their own log4js consumer that filters by appender name.
  • User impact of a change: Negligible.

8. Runtime symbols and codegen logical IDs (highest-impact area)

This is the area with the largest cross-package compatibility implications. Investigation summary (see appendix below):

  • 18 Symbol.for("cdktf/*") keys marking class identity for tree-walking type guards (e.g. TerraformStack.isStack(x), isTerraformResource(x)). Pattern is uniform: constructor writes the symbol, static method reads via SYMBOL in x.
  • 5 Symbol.for("@cdktf/core.TokenMap.*") keys used as both marker AND cache key for token caching.
  • Logical ID prefixes: __cdktf_module_asset (terraform-module-asset.ts:81), glob.__cdktfTokenMap (tokens/token-map.ts:40).

The cross-version concern:
An older @cdktn/provider-* package published when cdktn-cli wrote Symbol.for("cdktf/X") may not be recognized by a newer cdktn framework release that only writes/reads Symbol.for("cdktn/X"). Type guards return false, the construct tree walk misses the provider's resources, and synthesis silently produces wrong output. This is what would break if symbols flip without compatibility.

Known mitigation — dual-symbol pattern (feature-flag gated):
For the 11 class-identity symbols, the pattern is mechanically simple and bidirectionally compatible:

const OLD = Symbol.for("cdktf/TerraformElement");
const NEW = Symbol.for("cdktn/TerraformElement");
// Constructor: write both
Object.defineProperty(this, NEW, { value: true });
Object.defineProperty(this, OLD, { value: true });
// Type guard: read either
return NEW in x || OLD in x;

This means:

  • Old provider (writes cdktf/X only) + new core (checks both) → works
  • New provider (writes both) + old core (checks cdktf/X only) → works

For the 5 TokenMap symbols the same idea works but is more involved because the symbol is the data key, not just a marker — dual-writing the cached value at both keys, dual-reading on lookup. Solvable, but adds plumbing.

Logical ID prefixes (__cdktf_*) are different: they appear in synthesized output, so flipping them affects Terraform state. This is the strongest argument for a feature flag (new projects opt in; existing deployed stacks keep current IDs until the user runs a state rewrite).

Reference precedent: AWS CDK's constructs library uses Symbol.for("constructs.Construct") with the same duck-typing-across-realms pattern. Their evolution history is worth reviewing before settling on an approach.

Note: lessons learned from dual-dependency (cdktf + cdktn) design, which we wish to avoid going forward

9. Synthesized output filename: cdk.tf.json

  • Current: Synthesized files at <outdir>/stacks/<name>/cdk.tf.json. The prefix is cdk, not cdktf — it just means "produced by a CDK".
  • Tension: Likely none. cdk is generic; Terraform loads any *.tf.json in a directory regardless of prefix; the name doesn't carry the cdktf brand.
  • User impact of a change: Renaming to cdktn.tf.json would still synthesize correctly but would churn snapshots, downstream tooling that greps for cdk.tf.json, and documentation/examples — for no clear gain.
  • Surfaced for completeness; current expectation is no action.

10. Already at cdktn (no action)

For completeness:

  • CLI binary name (cdktn)
  • Telemetry product name (packages/@cdktn/commons/src/errors.ts:11, checkpoint.ts:84)
  • Debug output reports both cdktn and cdktf versions (debug.ts:393-399)

Cross-cutting open questions

These apply across multiple areas above and don't belong to any single sub-issue:

  1. Cadence: Each area can move independently across releases. Is there a desired ordering (e.g. read-path-only items first, state-affecting items last)?
  2. Deprecation timeline: When does cdktf-named fallback start emitting warnings? When (if ever) is it removed? Per-area or one timeline?
  3. Warning ergonomics: Print once per session vs. every invocation when legacy is detected.
  4. Versioning: Does a default flip warrant a major bump? Per area, or batched into one release?
  5. Documentation surface: When (and how) to update configuration-file.mdx, environment-variables.mdx, and the next upgrade guide.
  6. Contributor guidance: What is the rule today for someone adding a new env var or config key? (No current guidance — the absence is part of the bug.)
  7. State-rewrite UX: For items that change synthesized output (logical IDs in §8), what does the user experience look like for an existing deployed stack — print a terraform state mv plan? Require explicit opt-in? Refuse without backup confirmation?
  8. Prior art posture: SDD - 001-cdktn-package-rename/ (FR-008..FR-013 and research.md) explicitly preserves this surface. spec driven folders are left as frozen v0.22 record (ADR)

References


Appendix: Symbol.for() investigation

Investigation of all Symbol.for("cdktf/*") and Symbol.for("@cdktf/core.TokenMap.*") sites in packages/cdktn/src/, focused on whether a dual-symbol (write both, read either) backward-compat pattern is viable.

Class-identity symbols (11 sites, uniform pattern):
Every one is a class identity marker. Constructor writes Object.defineProperty(this, SYMBOL, { value: true }); a static type guard reads SYMBOL in x. Used for cross-realm/cross-package duck-typing (the cross-package equivalent of instanceof) in tree-walk predicates like findAll(TerraformProvider.isTerraformProvider).

  • app.ts:17,99Symbol.for("cdktf/App")
  • terraform-element.ts:10,37,52Symbol.for("cdktf/TerraformElement")
  • terraform-stack.ts:17,95,100Symbol.for("cdktf/TerraformStack")
  • terraform-resource.ts:40,157,174Symbol.for("cdktf/TerraformResource")
  • terraform-provider.ts:14,30,39Symbol.for("cdktf/TerraformProvider")
  • terraform-output.ts:12,46,50Symbol.for("cdktf/TerraformOutput")
  • terraform-count.ts:5,15,19Symbol.for("cdktf/TerraformCount")
  • terraform-dynamic-block.ts:9,23,70Symbol.for("cdktf/TerraformDynamicBlock")
  • terraform-dynamic-expression.ts:8,28,31Symbol.for("cdktf/TerraformDynamicExpression")
  • terraform-backend.ts:8,18,22Symbol.for("cdktf/TerraformBackend")
  • terraform-module-asset.ts:14,79,83Symbol.for("cdktf.TerraformModuleAsset") (used as data key on stack, not just marker — closer to the TokenMap pattern below)

Dual-symbol viability for class-identity sites: High. The change is mechanical — dual-write in the constructor, dual-read in the type guard. Symbol lookups are O(1). Bidirectional compatibility (old provider + new core, new provider + old core).

TokenMap symbols (5 sites, different pattern):
packages/cdktn/src/tokens/private/token-map.ts:20-105. The symbol is both marker AND property key for the cached valuecachedValue(x, sym, prod) reads from and writes to the same symbol slot on x.

  • @cdktf/core.TokenMap.STRING
  • @cdktf/core.TokenMap.LIST
  • @cdktf/core.TokenMap.NUMBER
  • @cdktf/core.TokenMap.NUMBER_LIST
  • @cdktf/core.TokenMap.MAP

Dual-symbol viability for TokenMap: Medium. Solvable by dual-writing the cached value under both keys and trying both on read. Adds plumbing, threading a legacy-symbol arg through cachedValue and each register* call. Failure mode without the fix: cache miss → token re-registered → potential plan-mismatch with duplicate token keys.

DependableTrait symbol (tokens/private/dependency.ts:54,94):
Symbol.for("@aws-cdk/core.DependableTrait") — used for trait storage. Same pattern as TokenMap.

Logical ID prefixes (separate from symbols, but related concern):

  • __cdktf_module_asset (terraform-module-asset.ts:81)
  • glob.__cdktfTokenMap (tokens/token-map.ts:40-41)

These land in synthesized output and therefore in Terraform state. Flipping them is a state-affecting change. Strongest case for feature-flag gating among all the items here.

Conclusion: A dual-symbol pattern is viable as a known mitigation for class-identity symbols (the bulk of the 18). TokenMap requires more care. Logical ID prefixes warrant a feature flag. None of this is decided here — surfacing for design discussion in the appropriate sub-issue.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions