Skip to content

feat(lib): validate Fn usage against Terraform/OpenTofu versions - #268

Merged
so0k merged 6 commits into
mainfrom
fix-generate-function-bindings
Jun 26, 2026
Merged

so0k merged 6 commits into
mainfrom
fix-generate-function-bindings

Conversation

@so0k

@so0k so0k commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Stacked on #269 (declared targetVersions foundation).

Terraform and OpenTofu have diverged in their built-in function sets, and the checked-in function metadata predated Terraform 1.8 — so Fn was missing every function added since, and using a function your targeted runtimes don't support only failed at terraform plan time. This PR fixes the bindings generator, regenerates the bindings to cover both products, and adds an opt-in synth-time validation of function usage against the project's declared targets.

Full Source data: open-constructs/cdktn-planning - RFCS/03-function-availability/PROPOSAL.md

Full function matrix: https://cdktn.io/function-matrix.html

Commits (reviewable independently)

  1. chore(lib): function availability dataset + tooling — swept metadata functions -json across every stable Terraform (1.5.7→1.15.6, 66 releases) and OpenTofu (1.6.0→1.12.1, 54 releases) release into functions-matrix.json. Includes a pnpm update-function-matrix delta updater for new releases (downloads only unseen versions) and the generator for the availability map. Baseline sweep scripts are marked linguist-generated and excluded from copywrite/prettier/eslint.
  2. feat(lib): regenerate function bindings from Terraform 1.15.6 — filters core:: aliases (terraform ≥1.8 reports every builtin twice; not valid identifiers), merges OpenTofu-only functions from the matrix. New on Fn and the cdktn convert map: convert, ephemeralasnull, issensitive, templatestring, base64gunzip, cidrcontains, urldecode. None are variadic, so no new INTERNAL_METHODS. Upstream metadata also refined format/formatlist return types (any → string/string[]; rendered expressions unchanged).
  3. feat(lib): validate Fn usage against declared targets (validateFunctionVersions flag) — a usage registry records Fn calls at the terraformFunction() chokepoint (~5ns/call, 0.04% of an Fn call); ValidateFunctionVersionSupport checks used functions from the generated availability map against the project's declared targetVersions via checkFeatureSupportedByTargets (range subset), with an availability hint pointing at the products/versions that do support the function. Purely declarative — no binary is ever executed; synth behaves identically in CI and environments without Terraform/OpenTofu installed. Added to FUTURE_FLAGS so cdktn init enables it for new projects.
  4. chore(tests): integration test + cross-version docs — every synth runs with a nonexistent TERRAFORM_BINARY_NAME, proving no binary involvement: the default baseline targets deterministically reject issensitive (introduced TF 1.8 / Tofu 1.7), and raising the declared floor in cdktf.json makes it pass.

Example validation error:

Validation failed with the following errors:
  [MyStack/resource] Terraform function "templatestring" requires terraform >=1.9.0, but the project targets terraform >=1.5.7. It is available in terraform >=1.9.0 and opentofu >=1.7.0.

Companion docs PR: open-constructs/cdk-terrain-docs

Test plan

  • pnpm nx test cdktn — 433/434 pass (the one failure is matchers.test.ts toPlanSuccessfully, which requires a running Docker daemon; environmental, unrelated)
  • 8 new unit tests in validations.test.ts: baseline functions never resolve targets (execSync spy), default-baseline rejection, declared-targets pass (no exec), product-exclusive with availability hint, OpenTofu-only targeting, malformed declared targets, flag on/off wiring
  • jsii build clean (nx run cdktn:build)
  • Delta updater verified: rewinding the matrix one release and re-running reconstructs it byte-identically
  • Integration test requires pnpm package dist (runs in CI); deterministic across the version matrix — no binary is executed

🤖 Generated with Claude Code

Comment thread packages/cdktn/src/features.ts Resolved
@so0k
so0k force-pushed the fix-generate-function-bindings branch from d18ecd9 to 7b9a82b Compare June 11, 2026 18:39
@sakul-learning

This comment has been minimized.

@so0k
so0k marked this pull request as draft June 11, 2026 23:54
@so0k

This comment was marked as resolved.

@so0k
so0k force-pushed the fix-generate-function-bindings branch from 7b9a82b to 30a4cb8 Compare June 12, 2026 00:11
@so0k
so0k changed the base branch from main to target-versions-foundation June 12, 2026 00:11
@so0k
so0k force-pushed the target-versions-foundation branch from 03dc31f to 12f9975 Compare June 12, 2026 00:30
@so0k
so0k force-pushed the fix-generate-function-bindings branch from 30a4cb8 to 72db7b9 Compare June 12, 2026 00:35
@so0k

This comment was marked as duplicate.

@so0k
so0k force-pushed the fix-generate-function-bindings branch from 72db7b9 to e2a5af1 Compare June 12, 2026 01:41
@so0k

This comment was marked as resolved.

@X-Guardian

Copy link
Copy Markdown
Contributor

this PR absorbs some of the work in #262 and I could make absorb all of it cc @X-Guardian

Up to you Vincent.

My only comment on this Pr would be why are we using Python scripts rather than Node?

@so0k

This comment was marked as resolved.

@X-Guardian

Copy link
Copy Markdown
Contributor

Personally, I would merge #262 first, and then refactor your changes on top, otherwise these PRs gain more and more bloat and are then difficult to review.

@so0k
so0k force-pushed the target-versions-foundation branch from 1af9ee5 to 4c23aa5 Compare June 13, 2026 04:13
@so0k
so0k force-pushed the fix-generate-function-bindings branch from 831c0e1 to d99e731 Compare June 13, 2026 04:28
@so0k

so0k commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

Personally, I would merge #262 first, and then refactor your changes on top, otherwise these PRs gain more and more bloat and are then difficult to review.

Done! #262 merged, #269 rebased to main (Ready to merge) and this PR stacked on that to land the missing function support from TF 1.5.x -> TF(latest)/OTF(latest) with synth time validation

Comment thread packages/cdktn/src/features.ts Resolved
@so0k
so0k force-pushed the target-versions-foundation branch from 4c23aa5 to 9171dcd Compare June 24, 2026 16:52
jsteinich pushed a commit that referenced this pull request Jun 25, 2026
…ion) (#269)

## Summary

> Reworks unreleased
#237 to not run
terraform binary during synth

Prerequisite for #268. Instead of probing whichever Terraform/OpenTofu
binary happens to be installed during synth, projects declare the
runtimes they intend to support, and validations answer: *"is this
feature supported by the project's declared target range?"* —
deterministically, with no binary required. Installed-binary
verification becomes an explicit opt-in for commands that execute the
binary anyway.

```jsonc
// cdktf.json
{
  "targetVersions": {
    "terraform": ">=1.5.7",
    "opentofu": ">=1.6.0"
  },
  "validateInstalledBinary": true // opt-in, off by default
}
```

### Commits

1. **feat(cli): add targetVersions to cdktf.json config** — typed field
in `@cdktn/commons`; `parseConfig` validates product names and
npm-semver ranges (Terraform's `~>` syntax is rejected with a hint — npm
semver would silently parse it as `~` with different semantics) and
threads the declaration to apps through the existing
`CDKTF_CONTEXT_JSON` channel. Also adds `parseTerraformCliVersion` to
commons and the `validateInstalledBinary` flag.
2. **feat(lib): validate features against declared runtime targets** —
`resolveTargetVersions(scope)` (context → dual-baseline default
`terraform >=1.5.7` + `opentofu >=1.6.0`),
`checkFeatureSupportedByTargets` (semver range **subset**: a feature
passes only if supported across the *entire* declared range of every
targeted product), and `ValidateFeatureTargetSupport` (an `IValidation`
that never executes a binary). Synth-time binary probing is removed:
`ValidateTerraformFeatureVersion` (merged unreleased in #237, zero call
sites) is deleted together with its execSync machinery — the pure
`parseTerraformCliVersion` parser and the
`TerraformFeatureVersionConstraints` type remain. The long-released
`ValidateBinaryVersion` and the resource-move probe built on it stay
unchanged as the legacy exception, both marked `TODO(target-versions)`.
3. **feat(cli): opt-in installed-binary verification** — when
`validateInstalledBinary: true`, `initalizeTerraform` (the
diff/deploy/destroy chokepoint, where the binary is about to run anyway)
verifies the installed product/version against the declared targets.
Declared targets are deliberately *not* treated as proof about the local
binary.
4. **feat(cli): write default targetVersions in init templates** — new
projects get the dual baseline explicitly in `cdktf.json`.

### Semantics

- **Subset check**: declaring `terraform: ">=1.5.7"` while using a
feature that needs `>=1.8.0` fails — the project claims to support
1.5.7..1.8.0 where the feature is missing. The fix is raising the
declared floor (or narrowing the range), which keeps the declaration
honest.
- **Missing product key in a feature's constraints** = unsupported by
that product; **missing product key in declared targets** = the project
doesn't target that product.
- **Undeclared** = dual product baseline (strictest, most portable
interpretation).
- **Synth never executes a binary.** The only places a binary is run are
commands that execute it anyway (opt-in `validateInstalledBinary`) and
the legacy `ValidateBinaryVersion`/resource-move path kept for
compatibility.

### Stacked work

#268 (function availability validation) is stacked on this branch: its
`ValidateFunctionVersionSupport` checks used `Fn` functions against the
declared targets via `checkFeatureSupportedByTargets`, gated by the
`validateFunctionVersions` feature flag.

## Test plan

- [x] `@cdktn/commons`: 83/83 — parser accepts constraint-shaped
targets, rejects unknown products / malformed ranges / `~>` / empty
declarations / non-strings; context payload merge
- [x] `cdktn`: 425/426 — resolver default + context override +
malformed-context errors; subset pass/fail; independent product
evaluation; product-not-targeted skip; complex ranges;
`ValidateFeatureTargetSupport` end-to-end with an execSync spy proving
no binary call (the 1 failure is the pre-existing
Docker-daemon-dependent `matchers.test.ts`, unrelated)
- [x] `@cdktn/cli-core`: 146 passed / 30 skipped (dist-dependent) —
installed-binary check helper:
satisfies/outside-range/undeclared-product/unparseable/default-baseline
- [ ] Integration: existing init tests exercise the new template field
in CI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Base automatically changed from target-versions-foundation to main June 25, 2026 02:15
…tooling

Vendor functions-matrix.json — a per-function availability map across every
stable Terraform (>=1.5.7) and OpenTofu (>=1.6.0) release — and the TypeScript
tooling that consumes and maintains it:

- update-function-matrix: delta updater for new releases (downloads only unseen
  versions into a temp dir, warns loudly if a function disappears)
- generate-function-availability: emits the version-constraint map consumed by
  the cdktn validation (only non-universal functions)
- matrix.ts: shared matrix location + types

The baseline data sweep that produced the matrix — the per-release
`metadata functions -json` dumps plus the build-matrix.py / build-report.py /
sweep.sh scripts and the interactive HTML report — lives in the
open-constructs/cdktn-planning repo (RFCS/function-availability); only the
resulting matrix is vendored here. A README in function-availability/ points
there. Mark the matrix linguist-generated and exclude the directory from
copywrite headers, prettier and eslint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
so0k and others added 4 commits June 25, 2026 17:48
The checked-in functions.json predated Terraform 1.8, so Fn was missing
every function added since. generate.ts now:

- drops `core::` namespaced aliases (terraform >=1.8 reports every builtin
  twice; the aliases are not valid identifiers)
- merges OpenTofu-only functions from the availability matrix so Fn covers
  both products, linking their docstrings to opentofu.org

New on Fn (and the hcl2cdk convert map): convert, ephemeralasnull,
issensitive, templatestring, base64gunzip, cidrcontains, urldecode.
None have variadic parameters, so no new INTERNAL_METHODS overrides.

Upstream metadata also refined return types: format now returns string and
formatlist string[] (previously any); rendered expressions are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e flag

Terraform and OpenTofu support different function sets (and introduce
shared functions at different versions). With the validateFunctionVersions
feature flag enabled, every TerraformStack validates the functions used
through Fn against the project's declared targetVersions instead of letting
`terraform plan` fail later:

- functions/usage-registry: records each Fn call at the terraformFunction()
  chokepoint (process-global by design; ~5ns per call)
- ValidateFunctionVersionSupport: checks only functions present in the
  generated availability map against the declared targets via
  checkFeatureSupportedByTargets (range subset), with an availability hint
  pointing at the products/versions that do support the function
- purely declarative: no binary is executed, so synth behaves identically
  in CI and environments without Terraform/OpenTofu installed (verifying
  the installed binary is the separate opt-in validateInstalledBinary CLI
  behavior)
- add flag to FUTURE_FLAGS so cdktn init enables it for new projects

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The test runs every synth with a nonexistent TERRAFORM_BINARY_NAME, proving
the validation never executes a binary: outcomes are driven purely by the
declared targetVersions in cdktf.json (default baseline fails for a
function introduced later; raising the declared floor makes it pass).

Document in test/Readme.md how the .terraform.versions.json matrix and the
CI Docker image binaries relate to TERRAFORM_BINARY_NAME, which tests
execute a binary, and that synth-time validations are deliberately not
version-sensitive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integration test sets a nonexistent TERRAFORM_BINARY_NAME to prove the
Fn version validation is purely declarative (no binary executed). But the
test's cdktf.json declared hashicorp/random, so setupTypescriptProject's
`cdktn get` tried to fetch that provider's schema via `terraform init` and
failed before any assertion ran.

main.ts never uses the random provider, so removing it makes `cdktn get` a
no-op and lets the nonexistent-binary premise hold across the entire flow
(init -> get -> synth), strengthening the "no binary executed" guarantee.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@so0k
so0k force-pushed the fix-generate-function-bindings branch from d99e731 to ea4e8df Compare June 25, 2026 12:12
@so0k
so0k marked this pull request as ready for review June 25, 2026 12:12
Comment thread test/Readme.md Resolved
…etVersions

Function version validation checks Fn usage against the declared
targetVersions using the vendored availability matrix; it never executes a
Terraform/OpenTofu CLI. Only the optional binary verification validation runs
<binary> version. Correct the stale wording in features.ts, the generator
docstring, and test/Readme.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@so0k
so0k enabled auto-merge (squash) June 25, 2026 15:38
@so0k
so0k merged commit cdf8115 into main Jun 26, 2026
514 of 516 checks passed
@so0k
so0k deleted the fix-generate-function-bindings branch June 26, 2026 02:24
so0k pushed a commit that referenced this pull request Jul 3, 2026
🤖 Release PR — merge to cut a new release. Kept open and rebased
as commits land on `main`.
---


<details><summary>0.23.4</summary>

##
[0.23.4](v0.23.3...v0.23.4)
(2026-07-02)


### Features

* **cli:** validate Terraform CLI feature versions
([#237](#237))
([32d434c](32d434c))
* declared Terraform/OpenTofu target versions (validation foundation)
([#269](#269))
([0aa2343](0aa2343))
* **lib:** S3-backend - Add `useLockfile` field and update
`dynamodbTable` deprecated JSDoc
([#234](#234))
([32a408f](32a408f))
* **lib:** validate Fn usage against Terraform/OpenTofu versions
([#268](#268))
([cdf8115](cdf8115))


### Bug Fixes

* **cli:** don't mistake terraform error output for a missing-variable
prompt
([#267](#267))
([0ea946f](0ea946f))
* **cli:** release terraform state lock when interrupting diff/deploy
([#284](#284))
([de617db](de617db))
* **cli:** resolve prebuilt Java provider versions via repo1.maven.org
([#285](#285))
([d5af07d](d5af07d))
* **gha:** Adding CHANGELOG.md to prettier ignore list
([#258](#258))
([5160469](5160469))
* **gha:** pull jsii-terraform CI image from GHCR instead of Docker Hub
([#261](#261))
([f19d34b](f19d34b))
* **provider-generator:** copy only publishable files into the jsii
compile bundle
([#292](#292))
([562d4c7](562d4c7))
* **provider-generator:** match shorthand provider sources against
OpenTofu schema keys
([#293](#293))
([fda0103](fda0103))
* **release:** add Changelog title for correct release-please insertion
([#255](#255))
([004818a](004818a))
* **release:** drop devEngines.packageManager that broke npm publish
([ef508a6](ef508a6))
* **tools:** repair generate-function-bindings tool
([#273](#273))
([821e1cf](821e1cf))


### Miscellaneous Chores

* Add Gradle to Mise config
([#271](#271))
([1578270](1578270))
* add pnpm to mise
([#266](#266))
([48ab91f](48ab91f))
* add uv to mise
([#260](#260))
([163cf3d](163cf3d))
* **deps:** bump nrwl/nx-set-shas from
38457b511e60ee4e1da09255b658e480748a1af4 to
afb73a62d26e41464e9254689e1fd6122ee683c1
([#236](#236))
([b5ac018](b5ac018))
* **deps:** bump the github-actions-backward-compatible group across 1
directory with 7 updates
([#235](#235))
([de67d9f](de67d9f))
* **deps:** replace uuid dependency with node:crypto.randomUUID
([#291](#291))
([6d3657a](6d3657a))
* ESLint ignore test edge-provider-bindings
([#270](#270))
([ba407bb](ba407bb))
* Fix Verdaccio logging config
([#272](#272))
([aded64b](aded64b))
* **gha:** parallelize provider binding generation for documentation
examples
([#244](#244))
([0a9c955](0a9c955))
* Migrate package manager from yarn to pnpm
([#170](#170))
([d23da4e](d23da4e))
* migrate planning artifacts to cdktn-planning [skip ci]
([968c358](968c358))
* **release:** add release-please automation for versioning
([#243](#243))
([c759eec](c759eec))
* Remove dependency-pinner tool
([#250](#250))
([42c27d5](42c27d5))
* Remove unused dependencies
([#281](#281))
([c7de65e](c7de65e))
* **tests:** run verdaccio in-process
([#248](#248))
([c17677d](c17677d))
* Update JSII version constraint
([#245](#245))
([d89558f](d89558f))
* Update scheduled workflows to only run on open-constructs/cdk-terrain
([#256](#256))
([ba408cf](ba408cf))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: open-constructs-cdktn[bot] <291052431+open-constructs-cdktn[bot]@users.noreply.github.com>
so0k added a commit that referenced this pull request Jul 23, 2026
…ons (RFC-04) (#296)

Implements RFC-04 — **provider feature availability**: support the newer
provider plugin-protocol capability families with `targetVersions`-aware
codegen and synth-time validation. Proposal, dataset, sweep tooling and
interactive report live in **open-constructs/cdktn-planning** →
`RFCS/04-provider-feature-availability/` (only the merged matrix is
vendored here, matching the function-availability split).

Builds directly on the #269 foundation (`targetVersions` +
`ValidateFeatureTargetSupport`) and the #268 usage-registry pattern.

## Guiding decision

**Generate the full surface the schema offers; narrow per project at
synth time via `targetVersions`.** Generation-time filtering would fork
the generated API by project configuration — impossible for prebuilt
providers and hostile to caching. Constraints baked into the validations
are the per-product `>=` ranges from the sweep dataset.

## Review guide (one commit per RFC rollout item)

1. **`chore: vendor provider-feature availability matrix`** — dataset
digest + README only, no behavior change.
2. **`feat(lib): TerraformEphemeralResource`** — new public base class
synthesizing to the top-level `ephemeral` key (JSON + HCL renderer),
refs as `ephemeral.<type>.<id>.<attr>`, no
provisioners/connection/import/move. Constructor registers the
target-version validation unconditionally (new API surface — only fires
on use, no feature flag per RFC). Internal `providerFeatureConstraints`
map sourced from the vendored matrix.
3. **`feat(provider-generator): acquire newer provider-protocol schema
sections`** (Phase 0, a bugfix on its own) — commons types for
`functions` / `ephemeral_resource_schemas` / `resource_identity_schemas`
/ `write_only`; sanitizer walks ephemeral schemas; fetched schemas
stamped with the fetching CLI; **cache key now includes CLI
product+minor** (a schema fetched once with an old CLI no longer poisons
every later generation); fetch-time warning when the fetching binary
structurally cannot emit sections the targets admit.
4. **`feat(provider-generator): generate ephemeral resource bindings`**
— third schema family mirroring the `data_` pipeline
(`EphemeralRandomPassword` in `ephemeral-random-password/`), config
extends `TerraformEphemeralMetaArguments`, no `generateConfigForImport`.
Zero churn in existing snapshots (ephemeral models append after
resources/data sources; class-name dedup is order-dependent).
5. **`feat(provider-generator): generate provider-defined function
bindings`** — `TimeProviderFunctions.rfc3339Parse(ts)` →
`${provider::time::rfc3339_parse(...)}`; namespace defaults to the
registry short name with a `providerLocalName` override (local names
change the namespace, aliases don't). Runtime flows through one public
jsii chokepoint (`TerraformProviderFunction.invoke`) feeding a usage
registry; `TerraformStack` validates usage against `terraform >=1.8.0` /
`opentofu >=1.7.0` — note the deliberate asymmetry: OpenTofu *language*
support (1.7.0) predates its schema *emission* (1.8.0), so generation
and validation use different boundaries.
6. **`feat(provider-generator): deprecate write-only attribute getters,
validate usage`** — providers never persist write-only values (every
read is `null` by protocol contract), so the state-backed getter is a
trap: emitted `@deprecated` now, removal rides the next prebuilt major
(JSII-breaking otherwise). Setting one (setter or constructor config)
registers usage via a new protected
`TerraformResource.registerProviderFeatureUsage` hook — generated code
reaches the validation machinery only by extending base classes.
7. **`feat(cli): thread targetVersions into cdktn get`** —
`GetOptions.targetVersions` → `ConstructsMaker` → `readSchema`, driving
the Phase 0 fetch-time warning; `constraints.json` gains diagnostic
stamps (`targetVersions`, fetching `cli`) without affecting the
`filterAlreadyGenerated` staleness logic (targets don't change codegen
output, so they must not force regeneration).

## Test coverage

- Generator: snapshot tests against **real `terraform providers schema
-json` fragments** from the sweep (random ephemeral, time functions
incl. object returns, vault `*_wo`), plus a synthetic fixture for
variadic/reserved-name mapping branches. Zero churn in pre-existing
snapshots.
- Core: validation matrices mirroring `validations.test.ts`
(admit/exclude per product, hint text, the OpenTofu 1.7.0 asymmetry,
registry resets).
- Schema: emission-gap logic matrix, cache-key suffix behavior,
sanitizer walk over ephemeral schemas; CLI stamps in network snapshots
are stubbed (environment-dependent).

## Known/deferred

- `matchers.test.ts › toPlanSuccessfully` fails in this environment
before and after these changes (downloads the real docker provider;
verified pre-existing via stash on the unmodified tree).
- Deferred per RFC Phase 4: resource identity consumption (schema
plumbing already carried), list resources / actions / state stores
(Terraform-only today; re-evaluate on opentofu/opentofu#3787).
- **Follow-ups tracked as issues**: #308 (nested write-only
registration), #309 (matrix drift check / in-repo tooling), #310
(`hcl2cdk` ephemeral conversion), #311 (edge-provider schema
cross-language coverage), #312 (ephemeral integration test gated on TF
>= 1.10), #313 (pre-existing HCL renderer defects), #304 (first-class
`provider_meta`; #314 closed as its duplicate), #316 (remove the
deprecated write-only getters at the next prebuilt-provider major — the
Phase 3 end state). New this round: #336 (structural typing for
object-shaped function params/returns), #337 (runtime CI thin slice on
terraform 1.15.x + opentofu 1.12.x — CI's Terraform ceiling is 1.6.5, so
the new schema sections are covered by fixture unit tests, the
edge-provider compile slice below, and the external demo harness until
#337 lands).

## Review findings addressed (from the demo-harness verification
comment)

- **Fixed — `targetVersions` validation bypass**:
`CdktfConfig.targetVersions` (the `runGetInDir` path) now validates via
commons `validateTargetVersions` (warn + ignore), and the emission-gap
check treats invalid ranges as not-wanted instead of throwing — a
malformed range previously crashed `cdktn get` via `semver.intersects`.
- **Fixed — ephemeral lifecycle narrowed**: new
`TerraformEphemeralResourceLifecycle` (`precondition`/`postcondition`
only) replaces the full managed-resource lifecycle on the ephemeral API,
before it ships as jsii surface.
- **Fixed (docs) — provider-fn self-reference cycle**: generated
provider-function JSDoc and `TerraformProviderFunction.invoke` now warn
against calling a provider's functions inside that same provider's
configuration block.
- **Documented — ephemeral × write-only**: registration deliberately
skips ephemeral resources — write-only is a state concept, ephemeral
resources have no state, and no schema in the sweep (incl. vault's 16
ephemeral resources) combines the two.
- **Follow-up — nested write-only registration** (#308): `write_only`
inside nested blocks gets the deprecated getter but skips usage
registration; deep config scanning deserves its own PR (a miss degrades
to the plan-time error, not silent breakage).
- **Pre-existing / by design**: the AWS barrel-import OOM predates this
PR (lazy-index is the existing mitigation); `Fn.ephemeralasnull` +
`sensitive` on outputs is Terraform semantics (docs candidate); list
resources / actions / resource identity codegen is the RFC Phase 4
deferral noted above.

## Second review round addressed

- **Fixed — cross-App usage-registry leak**: `App` construction now
resets both usage registries (a new App = a new synthesis session).
Within an App the process-global registry is by design — every stack
resolves the same `targetVersions` from App context — but usage no
longer leaks into later, unrelated Apps in the same process (the
reviewer's jest scenario). Regression tests cover the exact sequence;
the flag-gated `Fn` registry had the same latent leak and is reset too.
- **Fixed — emission-gap warning on cache hits**: the warning moved from
`readProviderSchema` into `readSchema`, running for cached *and* fresh
schemas using the `cli_name`/`cli_version` stamps the schema carries.
Regression test: two `readSchema` calls against a cache dir → one
producer call, two warnings.
- **Docs — matrix as source of truth**: both hand-maintained maps now
name `features-matrix.json` as their source and cross-reference each
other; automated drift check tracked in #309.

## Third review round addressed (cfncompat/awscc demo findings)

- **Fixed — dynamic-typed provider-function returns coerced to string**:
generated wrappers for `dynamic`/`object`/`map` return types no longer
wrap in `Token.asString(...)`; they return the raw `invoke()`
`IResolvable`, which `Tokenization.isResolvable()` recognizes —
struct-typed attribute assignments (`OutputReference.internalValue`) no
longer vanish silently from synth output.
- **Fixed — `invoke()` dropped literal `null` positional arguments**:
arguments are now validated per position (variadic flattening stays in
the generated wrappers where the signature is known), and a latent third
bug this exposed was fixed too: `FunctionCall` rendered args via
`Array.prototype.join`, which collapses `null` entries to empty strings
— resolved `null`/`undefined` now render as the Terraform `null`
keyword. Regression tests pin the exact
`provider::cfncompat::condition_if(true, null, {"a" = 1})` rendering.


## Fourth review round addressed (jsteinich's CHANGES_REQUESTED)

Seven commits on top of the reviewed head, one per finding group (inline
replies on each thread carry the details):

1. **Schema fidelity** — `FunctionParameter` gains `is_nullable`,
`FunctionSignature` gains `deprecation_message`, and both drop
`description_kind` (verified against
`internal/command/jsonfunction/{function,parameter}.go` and the OpenTofu
equivalent: that field does not exist on function JSON;
`allow_null_value`/`allow_unknown_values` are protocol-only names — the
old comment was wrong). OpenTofu emits no `deprecation_message` at all
(divergence, documented).
2. **Recursive, honest type mapping** — collections recurse
(`list(number)` → `number[]` via `Token.asNumberList`, `set(bool)` param
→ `Array<boolean | IResolvable>`, nested compose); object/map/dynamic
returns are declared `IResolvable` instead of `any` (property access on
a token is now a compile error); `is_nullable` consumed (trailing →
jsii-optional, mid-position → `any`, variadic → `any[]`, docstrings say
`T | null`); `deprecation_message` → `@deprecated`; name collisions
hard-fail generation with an actionable error. Structural helpers for
object shapes: #336.
3. **App-owned usage registries** — recording moved to token-resolve
time keyed by the resolving stack's `node.root` (WeakMaps); the
App-constructor resets and the "pathological" disclaimer are gone; the
interleaved-App false negative is structurally impossible and pinned by
regression tests for both registries.
4. **Functions move onto the provider class** —
`time.functions.rfc3339Parse(ts)` via a memoized getter fed by
`this.terraformResourceType` (the exact `required_providers` key, so the
namespace is correct by construction); the static class and
`providerLocalName` parameter are gone, mooting the `providerAlias`
naming question. Per-method self-reference JSDoc dropped (kept once on
`invoke`; docs PR: open-constructs/cdk-terrain-docs#29).
5. **Cleanups** — `registerProviderFeatureUsage` moved to
`TerraformElement` (ephemeral resources reuse it); enum casts replaced
by a membership guard + direct `ProviderFeature` indexing; hints get
their own map instead of runtime label-mangling (messages
byte-identical); write-only registration treats explicit `null` as
omission (`!= null`, both constructor and setter paths, four pinned
tests).
6. **Demo-validation fixes** — exercising the generated bindings in a
real app ([demo
PR](sakul-learning/cdktn-provider-features-demo#5))
caught three bugs snapshots couldn't: the sibling import needed `/index`
under `nodenext`; the wrapper constructor's TS parameter property is
rejected by Node's native type-stripping (jsii accepts it — Node is the
stricter consumer); variadic rest params need `Array<T>` for union
element types.
7. **Edge-provider compile coverage** (closes #311) — the edge schema
now carries nine functions (one per generator branch, incl. all three
nullability shapes and deprecation), an ephemeral resource, and a
`write_only` attribute; its bindings ride the existing postbuild jsii +
pacmak flow for all five languages and the `test/<lang>/edge`
native-compile suites, independent of CI's Terraform version.

**External validation** (full matrix in the [demo
PR](sakul-learning/cdktn-provider-features-demo#5)):
terraform 1.15.7 and opentofu 1.12.3 generate byte-identical bindings
modulo registry-host doc links; schema cache keys per CLI product+minor;
omitted/explicit/mid-position nullable args all render the bare `null`
keyword in position; generated bindings pass tsc + jsii + pacmak; the
targetVersions validation error was demonstrated live through the
resolve-time registry.

## Resolves Terraform CDK issues

- hashicorp/terraform-cdk#3886
- hashicorp/terraform-cdk#3686

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

I'm going to lock this pull request because it has been closed for 30 days. This helps our maintainers find and focus on the active issues. If you've found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Jul 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants