Plugin Framework State upgraders

I have a state upgrader which is converting from an sdkv2 provider with repeated blocks to a framework (v0.17.0) with nested attributes.

It works for the most part however I am seeing weird behaviour where it marks the nested attribute itself as changed but with no explicit changes.

Is there a way I can configure the test framework to tell me specifically what is different?

 ~ resource "pingfederate_oauth_authentication_policy_contract_mapping" "test" {
                id                                 = "lG2s4menuXtZ7Wno"
              ~ issuance_criteria                  = {
                  ~ conditional_criteria = [
                      ~ {
                            # (4 unchanged attributes hidden)
                        },
                    ]
                  ~ expression_criteria  = [
                      ~ {
                            # (1 unchanged attribute hidden)
                        },
                        # (1 unchanged element hidden)
                    ]
                }
              ~ ldap_attribute_sources             = [
                  ~ {
                        id                     = "ldap"
                        # (5 unchanged attributes hidden)
                    },
                ]
                # (3 unchanged attributes hidden)
            }

SDKv2

resource "pingfederate_oauth_authentication_policy_contract_mapping" "test" {
  authentication_policy_contract_ref {
    id = pingfederate_authentication_policy_contract.demo.id
  }
  attribute_contract_fulfillment {
    key_name = "USER_NAME"
    source {
      type = "AUTHENTICATION_POLICY_CONTRACT"
    }
    value = "subject"
  }
  attribute_contract_fulfillment {
    key_name = "USER_KEY"
    source {
      type = "AUTHENTICATION_POLICY_CONTRACT"
    }
    value = "subject"
  }
  ldap_attribute_source {
    description            = "desc"
    id                     = "ldap"
    member_of_nested_group = false
    search_filter          = "uid=$${subject}"
    search_scope           = "SUBTREE"

    data_store_ref {
      id = pingfederate_ldap_data_store.example.id
    }
  }
  jdbc_attribute_source {
    description = "jdbc"
    filter      = "uid=$${email}"
    id          = "jdbc"
    schema      = "INFORMATION_SCHEMA"
    table       = "ADMINISTRABLE_ROLE_AUTHORIZATIONS"

    data_store_ref {
      id = "ProvisionerDS"
    }
  }

  issuance_criteria {
    conditional_criteria {
      attribute_name = "Subject DN"
      condition      = "EQUALS"
      value          = "foo"

      source {
        id   = "ldap"
        type = "LDAP_DATA_STORE"
      }
    }
    expression_criteria {
      expression = "far"
    }
    expression_criteria {
      error_result = "woot"
      expression   = "bar"
    }
  }
}

Framework

resource "pingfederate_oauth_authentication_policy_contract_mapping" "test" {
  authentication_policy_contract_ref = pingfederate_authentication_policy_contract.demo.id
  attribute_contract_fulfillment = {
    "USER_NAME" = {
      source = {
        type = "AUTHENTICATION_POLICY_CONTRACT"
      }
      value = "subject"
    },
    "USER_KEY" = {
      source = {
        type = "AUTHENTICATION_POLICY_CONTRACT"
      }
      value = "subject"
    }
  }
  jdbc_attribute_sources = [{
    description    = "jdbc"
    filter         = "uid=$${email}"
    id             = "jdbc"
    schema         = "INFORMATION_SCHEMA"
    table          = "ADMINISTRABLE_ROLE_AUTHORIZATIONS"
    data_store_ref = "ProvisionerDS"
  }]
  ldap_attribute_sources = [{
    description            = "desc"
    id                     = "ldap"
    member_of_nested_group = false
    search_filter          = "uid=$${subject}"
    search_scope           = "SUBTREE"
    data_store_ref         = pingfederate_ldap_data_store.example.id
  }]

  issuance_criteria = {
    conditional_criteria = [{
      attribute_name = "Subject DN"
      condition      = "EQUALS"
      value          = "foo"

      source = {
        id   = "ldap"
        type = "LDAP_DATA_STORE"
      }
    }]
    expression_criteria = [{
      expression = "far"
      },
      {
        error_result = "woot"
        expression   = "bar"
    }]
  }
}

If anyone else comes across something like this, I found my issue, I was missing some defaults which would cause the apply to fail later on. The plan was just misleading.

╷
│ Error: Provider produced inconsistent result after apply
│ 
│ When applying changes to pingfederate_oauth_authentication_policy_contract_mapping.test, provider "provider[\"registry.terraform.io/iwarapter/pingfederate\"]" produced an unexpected new value: .issuance_criteria.expression_criteria[0].error_result: was
│ null, but now cty.StringVal("").

Please note that issues with the plan output missing value differences with framework-based providers can be submitted into the Terraform core issue tracker for resolution. One such case, which seems related to the fix mentioned here, is CLI Plan Output Not Showing null to "" Differences for Non-Legacy Type System Providers · Issue #31887 · hashicorp/terraform · GitHub.