Running the tfcompat suite through the real terraform-provider plugin (parameterized reattach path, #505) surfaced a family of deterministic output divergences that the in-process bridged path masked. The common shape: where OpenTofu materializes an absent or emptied nested collection as an empty list/set, the dynamic-bridge path materializes it as null (the pf property projection drops nested empty collections, and pulumi-hcl projects the missing property as null).
Three skipped tfcompat cases reproduce it (each has a putest twin locking in the current behavior):
tests/tfcompat/l2_omitted_nested_block — an optional MaxItems=1 nested block (rule) left out of an enclosing block: OpenTofu reads policy[0].rule as [] (length 0); pulumi-hcl reads null, so length(policy[0].rule) fails the deploy with Invalid value for "value" parameter: argument must not be null.
tests/tfcompat/l2_optional_computed_block_unset — an Optional+Computed MaxItems=1 TypeSet block the provider leaves unset: OpenTofu keeps an empty set (== null false, jsonencode → "[]"); pulumi-hcl reads null. (The TypeList variant agrees on null in both.)
tests/tfcompat/l2_ignore_changes_forcenew_block_removed — removing a MaxItems=1 block whose ForceNew attribute is in ignore_changes: both runtimes replace the resource and create the replacement with settings = [], but pulumi-hcl's settings output still reports the removed block's old value where OpenTofu reports [].
To reproduce, re-enable any of the three skipped tests and run it with terraform-provider ≥ 1.3.0 installed:
go test ./tests/tfcompat/ -run TestL2OmittedNestedBlock -v -count=1
The correct (OpenTofu-matching) behavior is pinned against the linked-in bridged provider by tests/putest/l2_block_projection_test.go — the divergence is specific to the plugin path. When this is fixed, re-enable the tfcompat cases and drop the putest twins.
Running the tfcompat suite through the real
terraform-providerplugin (parameterized reattach path, #505) surfaced a family of deterministic output divergences that the in-process bridged path masked. The common shape: where OpenTofu materializes an absent or emptied nested collection as an empty list/set, the dynamic-bridge path materializes it as null (the pf property projection drops nested empty collections, and pulumi-hcl projects the missing property as null).Three skipped tfcompat cases reproduce it (each has a putest twin locking in the current behavior):
tests/tfcompat/l2_omitted_nested_block— an optional MaxItems=1 nested block (rule) left out of an enclosing block: OpenTofu readspolicy[0].ruleas[](length 0); pulumi-hcl reads null, solength(policy[0].rule)fails the deploy withInvalid value for "value" parameter: argument must not be null.tests/tfcompat/l2_optional_computed_block_unset— an Optional+Computed MaxItems=1 TypeSet block the provider leaves unset: OpenTofu keeps an empty set (== nullfalse,jsonencode→"[]"); pulumi-hcl reads null. (The TypeList variant agrees on null in both.)tests/tfcompat/l2_ignore_changes_forcenew_block_removed— removing a MaxItems=1 block whose ForceNew attribute is inignore_changes: both runtimes replace the resource and create the replacement withsettings = [], but pulumi-hcl'ssettingsoutput still reports the removed block's old value where OpenTofu reports[].To reproduce, re-enable any of the three skipped tests and run it with
terraform-provider≥ 1.3.0 installed:go test ./tests/tfcompat/ -run TestL2OmittedNestedBlock -v -count=1The correct (OpenTofu-matching) behavior is pinned against the linked-in bridged provider by
tests/putest/l2_block_projection_test.go— the divergence is specific to the plugin path. When this is fixed, re-enable the tfcompat cases and drop the putest twins.