Skip to content

feat: add cost_optimization_features to dbtcloud_job, fix CI/Merge SAO and deferral bugs (closes #664)#670

Open
trouze wants to merge 6 commits into
mainfrom
feat/issue-664-cost-optimization
Open

feat: add cost_optimization_features to dbtcloud_job, fix CI/Merge SAO and deferral bugs (closes #664)#670
trouze wants to merge 6 commits into
mainfrom
feat/issue-664-cost-optimization

Conversation

@trouze
Copy link
Copy Markdown
Contributor

@trouze trouze commented Apr 9, 2026

Summary

  • Adds cost_optimization_features Set attribute as the preferred replacement for deprecated force_node_selection to enable State-Aware Orchestration (SAO). The attribute bridges to force_node_selection under the hood since the dbt Cloud API uses force_node_selection: bool — setting ["node_selection"] enables SAO, and the provider reads force_node_selection back to keep state consistent
  • Fixes CI/Merge jobs erroring with 405 when SAO validation runs — SAO fields are now skipped for CI/Merge job types
  • Fixes CI/Merge jobs losing deferring_environment_id after apply
  • Adds 5 new acceptance tests (note: clearing cost_optimization_features is not tested as the acceptance test account has account-level SAO enforcement that prevents disabling force_node_selection)

Closes #664

🤖 Generated with Claude Code

@trouze trouze requested a review from a team as a code owner April 9, 2026 19:55
@will-sargent-dbtlabs will-sargent-dbtlabs self-requested a review April 24, 2026 16:50
@boxysean
Copy link
Copy Markdown
Member

Hi @dbt-labs/cloud-signals @will-sargent-dbtlabs my client is asking for an update on when this will be merged. Can you let us know? thanks!

Copy link
Copy Markdown
Contributor

@gravelbit gravelbit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I run tests locally and they seem to fail, see logs. Do I need some specific flag in my environment?

TF_LOG=DEBUG TF_ACC=1 go test -run TestAccDbtCloudJobCostOptimizationFeatures ./pkg/framework/objects/job/...
2026-05-21T17:41:05.809+0300 [DEBUG] sdk.helper_resource: Starting TestCase: test_name=TestAccDbtCloudJobCostOptimizationFeatures
2026-05-21T17:41:05.809+0300 [DEBUG] sdk.helper_resource: Calling TestCase PreCheck: test_name=TestAccDbtCloudJobCostOptimizationFeatures
2026-05-21T17:41:05.809+0300 [DEBUG] sdk.helper_resource: Called TestCase PreCheck: test_name=TestAccDbtCloudJobCostOptimizationFeatures
2026-05-21T17:41:05.810+0300 [DEBUG] sdk.helper_resource: Found Terraform CLI: test_terraform_path=/opt/homebrew/bin/terraform test_name=TestAccDbtCloudJobCostOptimizationFeatures
2026-05-21T17:41:06.380+0300 [DEBUG] sdk.helper_resource: Starting TestStep: test_terraform_path=/opt/homebrew/bin/terraform test_working_directory=/var/folders/hd/<sr>/T/plugintest3880779166 test_name=TestAccDbtCloudJobCostOptimizationFeatures test_step_number=1
2026-05-21T17:41:06.380+0300 [DEBUG] sdk.helper_resource: Running Terraform CLI plan and apply: test_name=TestAccDbtCloudJobCostOptimizationFeatures test_step_number=1 test_terraform_path=/opt/homebrew/bin/terraform test_working_directory=/var/folders/hd/<sr>/T/plugintest3880779166
2026-05-21T17:41:06.482+0300 [INFO]  dbtcloud: Configuring dbt Cloud client: tf_req_id=e013599c-e66a-a69b-d58d-615ddb5aa646 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ConfigureProvider
2026-05-21T17:41:06.483+0300 [INFO]  dbtcloud: Configured dbt Cloud client: tf_req_id=e013599c-e66a-a69b-d58d-615ddb5aa646 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ConfigureProvider success=true
2026-05-21T17:41:06.483+0300 [DEBUG] sdk.framework: Marking Computed attributes with null configuration values as unknown (known after apply) in the plan to prevent potential Terraform errors: tf_req_id=ab5a639c-aaf7-c6e9-1de1-10f70ce7b04f tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_project
2026-05-21T17:41:06.483+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_resource_type=dbtcloud_project tf_attribute_path="AttributeName(\"type\")" tf_req_id=ab5a639c-aaf7-c6e9-1de1-10f70ce7b04f tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange
2026-05-21T17:41:06.483+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_attribute_path="AttributeName(\"description\")" tf_req_id=ab5a639c-aaf7-c6e9-1de1-10f70ce7b04f tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_project
2026-05-21T17:41:06.483+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_req_id=ab5a639c-aaf7-c6e9-1de1-10f70ce7b04f tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_project tf_attribute_path="AttributeName(\"id\")"
2026-05-21T17:41:06.485+0300 [DEBUG] sdk.framework: Marking Computed attributes with null configuration values as unknown (known after apply) in the plan to prevent potential Terraform errors: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_req_id=6b9a579b-fb3e-0f96-5dd9-0d5d90ad920e tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:06.485+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"credential_id\")" tf_req_id=6b9a579b-fb3e-0f96-5dd9-0d5d90ad920e tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:06.485+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"id\")" tf_req_id=6b9a579b-fb3e-0f96-5dd9-0d5d90ad920e tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:06.485+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_req_id=6b9a579b-fb3e-0f96-5dd9-0d5d90ad920e tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"extended_attributes_id\")"
2026-05-21T17:41:06.485+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"primary_profile_id\")" tf_req_id=6b9a579b-fb3e-0f96-5dd9-0d5d90ad920e tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:06.485+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"environment_id\")" tf_req_id=6b9a579b-fb3e-0f96-5dd9-0d5d90ad920e tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:06.486+0300 [DEBUG] sdk.framework: Marking Computed attributes with null configuration values as unknown (known after apply) in the plan to prevent potential Terraform errors: tf_req_id=593cc3f7-4b80-5dd2-1814-c5bb57279809 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_job
2026-05-21T17:41:06.486+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_attribute_path="AttributeName(\"self_deferring\")" tf_req_id=593cc3f7-4b80-5dd2-1814-c5bb57279809 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_job
2026-05-21T17:41:06.486+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_req_id=593cc3f7-4b80-5dd2-1814-c5bb57279809 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_job tf_attribute_path="AttributeName(\"job_type\")"
2026-05-21T17:41:06.486+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_job tf_attribute_path="AttributeName(\"id\")" tf_req_id=593cc3f7-4b80-5dd2-1814-c5bb57279809 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:06.486+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_job tf_attribute_path="AttributeName(\"job_id\")" tf_req_id=593cc3f7-4b80-5dd2-1814-c5bb57279809
2026-05-21T17:41:06.486+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_attribute_path="AttributeName(\"force_node_selection\")" tf_req_id=593cc3f7-4b80-5dd2-1814-c5bb57279809 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_job
2026-05-21T17:41:06.655+0300 [INFO]  dbtcloud: Configuring dbt Cloud client: tf_req_id=2b3ae85e-5ff8-2f98-e43b-75c97e454126 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ConfigureProvider
2026-05-21T17:41:06.655+0300 [INFO]  dbtcloud: Configured dbt Cloud client: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ConfigureProvider success=true tf_req_id=2b3ae85e-5ff8-2f98-e43b-75c97e454126
2026-05-21T17:41:06.655+0300 [DEBUG] sdk.framework: Marking Computed attributes with null configuration values as unknown (known after apply) in the plan to prevent potential Terraform errors: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_project tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_req_id=46aedccd-302c-ed07-5066-5cfb096d0bb3
2026-05-21T17:41:06.655+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_req_id=46aedccd-302c-ed07-5066-5cfb096d0bb3 tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_project tf_attribute_path="AttributeName(\"type\")"
2026-05-21T17:41:06.655+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_req_id=46aedccd-302c-ed07-5066-5cfb096d0bb3 tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_project tf_attribute_path="AttributeName(\"description\")" tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:06.655+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_req_id=46aedccd-302c-ed07-5066-5cfb096d0bb3 tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_project tf_attribute_path="AttributeName(\"id\")"
2026-05-21T17:41:07.292+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_rpc=ApplyResourceChange tf_resource_type=dbtcloud_project tf_req_id=830f819e-5664-2031-e22b-20f6295a38a2 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_attribute_path=name
2026-05-21T17:41:07.292+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_attribute_path=dbt_project_subdirectory tf_req_id=830f819e-5664-2031-e22b-20f6295a38a2 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange tf_resource_type=dbtcloud_project
2026-05-21T17:41:07.302+0300 [DEBUG] sdk.framework: Marking Computed attributes with null configuration values as unknown (known after apply) in the plan to prevent potential Terraform errors: tf_req_id=6d7e0689-ffb0-3fce-e2d6-e6a9735eff25 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment
2026-05-21T17:41:07.302+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"credential_id\")" tf_req_id=6d7e0689-ffb0-3fce-e2d6-e6a9735eff25 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange
2026-05-21T17:41:07.302+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_attribute_path="AttributeName(\"extended_attributes_id\")" tf_req_id=6d7e0689-ffb0-3fce-e2d6-e6a9735eff25 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment
2026-05-21T17:41:07.302+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"id\")" tf_req_id=6d7e0689-ffb0-3fce-e2d6-e6a9735eff25 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:07.302+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_req_id=6d7e0689-ffb0-3fce-e2d6-e6a9735eff25 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"environment_id\")"
2026-05-21T17:41:07.302+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=PlanResourceChange tf_resource_type=dbtcloud_environment tf_attribute_path="AttributeName(\"primary_profile_id\")" tf_req_id=6d7e0689-ffb0-3fce-e2d6-e6a9735eff25
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_attribute_path=project_id tf_resource_type=dbtcloud_environment tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_attribute_path=name tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange tf_resource_type=dbtcloud_environment
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_attribute_path=use_custom_branch tf_resource_type=dbtcloud_environment tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_attribute_path=enable_model_query_history tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange tf_resource_type=dbtcloud_environment
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange tf_resource_type=dbtcloud_environment tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_attribute_path=type
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_attribute_path=dbt_version tf_rpc=ApplyResourceChange tf_resource_type=dbtcloud_environment tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_attribute_path=connection_id tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange tf_resource_type=dbtcloud_environment
2026-05-21T17:41:07.607+0300 [DEBUG] sdk.framework: Value switched to prior value due to semantic equality logic: tf_resource_type=dbtcloud_environment tf_req_id=f091b55b-2d6f-7e4c-def1-1da94f44bd15 tf_attribute_path=is_active tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange
2026-05-21T17:41:07.621+0300 [DEBUG] sdk.framework: Marking Computed attributes with null configuration values as unknown (known after apply) in the plan to prevent potential Terraform errors: tf_req_id=248e5bc2-0d54-d438-8557-95baea4dd287 tf_rpc=PlanResourceChange tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_resource_type=dbtcloud_job
2026-05-21T17:41:07.621+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_resource_type=dbtcloud_job tf_req_id=248e5bc2-0d54-d438-8557-95baea4dd287 tf_rpc=PlanResourceChange tf_attribute_path="AttributeName(\"job_id\")"
2026-05-21T17:41:07.621+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_attribute_path="AttributeName(\"force_node_selection\")" tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_resource_type=dbtcloud_job tf_req_id=248e5bc2-0d54-d438-8557-95baea4dd287 tf_rpc=PlanResourceChange
2026-05-21T17:41:07.621+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_rpc=PlanResourceChange tf_attribute_path="AttributeName(\"id\")" tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_resource_type=dbtcloud_job tf_req_id=248e5bc2-0d54-d438-8557-95baea4dd287
2026-05-21T17:41:07.621+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_req_id=248e5bc2-0d54-d438-8557-95baea4dd287 tf_rpc=PlanResourceChange tf_attribute_path="AttributeName(\"self_deferring\")" tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_resource_type=dbtcloud_job
2026-05-21T17:41:07.621+0300 [DEBUG] sdk.framework: marking computed attribute that is null in the config as unknown: tf_req_id=248e5bc2-0d54-d438-8557-95baea4dd287 tf_rpc=PlanResourceChange tf_attribute_path="AttributeName(\"job_type\")" tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_resource_type=dbtcloud_job
2026-05-21T17:41:08.491+0300 [ERROR] sdk.proto: Response contains error diagnostic: tf_resource_type=dbtcloud_job diagnostic_detail="Could not create job, unexpected error: unexpected status code 405: {\"status\":{\"code\":405,\"is_success\":false,\"user_message\":\"node_selection are not valid features.\",\"developer_message\":\"\"},\"data\":{},\"extra\":{},\"error_code\":null}, URL: https://<host>/api/v2/accounts/<acc_id>/jobs/" diagnostic_severity=ERROR diagnostic_summary="Error creating job" tf_proto_version=6.11 tf_req_id=846284a2-59da-894b-6e8d-bd487de10c0e tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ApplyResourceChange
2026-05-21T17:41:08.514+0300 [WARN]  sdk.helper_resource: Error running Terraform CLI command: test_step_number=1 test_terraform_path=/opt/homebrew/bin/terraform test_working_directory=/var/folders/hd/<sr>/T/plugintest3880779166 test_name=TestAccDbtCloudJobCostOptimizationFeatures
  error=
  | exit status 1
  |
  | Error: Error creating job
  |
  |   with dbtcloud_job.test_job,
  |   on terraform_plugin_test.tf line 23, in resource "dbtcloud_job" "test_job":
  |   23: resource "dbtcloud_job" "test_job" {
  |
  | Could not create job, unexpected error: unexpected status code 405:
  | {"status":{"code":405,"is_success":false,"user_message":"node_selection are
  | not valid
  | features.","developer_message":""},"data":{},"extra":{},"error_code":null},
  | URL: https://<host>/api/v2/accounts/<acc_id>/jobs/

2026-05-21T17:41:08.514+0300 [ERROR] sdk.helper_resource: Unexpected error: test_terraform_path=/opt/homebrew/bin/terraform test_working_directory=/var/folders/hd/<sr>/T/plugintest3880779166 test_name=TestAccDbtCloudJobCostOptimizationFeatures test_step_number=1
  error=
  | Error running apply: exit status 1
  |
  | Error: Error creating job
  |
  |   with dbtcloud_job.test_job,
  |   on terraform_plugin_test.tf line 23, in resource "dbtcloud_job" "test_job":
  |   23: resource "dbtcloud_job" "test_job" {
  |
  | Could not create job, unexpected error: unexpected status code 405:
  | {"status":{"code":405,"is_success":false,"user_message":"node_selection are
  | not valid
  | features.","developer_message":""},"data":{},"extra":{},"error_code":null},
  | URL: https://<host>/api/v2/accounts/<acc_id>/jobs/

2026-05-21T17:41:08.982+0300 [INFO]  dbtcloud: Configuring dbt Cloud client: tf_req_id=015a8cfa-dd7c-49e4-0c43-905bb1833402 tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ConfigureProvider
2026-05-21T17:41:08.982+0300 [INFO]  dbtcloud: Configured dbt Cloud client: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_rpc=ConfigureProvider tf_req_id=015a8cfa-dd7c-49e4-0c43-905bb1833402 success=true
2026-05-21T17:41:08.987+0300 [INFO]  dbtcloud: Configuring dbt Cloud client: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_req_id=08b8cb07-3577-609b-09dd-a1dbbe11b7fe tf_rpc=ConfigureProvider
2026-05-21T17:41:08.987+0300 [INFO]  dbtcloud: Configured dbt Cloud client: tf_provider_addr=registry.terraform.io/hashicorp/dbtcloud tf_req_id=08b8cb07-3577-609b-09dd-a1dbbe11b7fe tf_rpc=ConfigureProvider success=true
2026-05-21T17:41:09.710+0300 [DEBUG] sdk.helper_resource: Calling TestCase CheckDestroy: test_terraform_path=/opt/homebrew/bin/terraform test_working_directory=/var/folders/hd/<sr>/T/plugintest3880779166 test_name=TestAccDbtCloudJobCostOptimizationFeatures test_step_number=1
2026-05-21T17:41:09.710+0300 [DEBUG] sdk.helper_resource: Called TestCase CheckDestroy: test_name=TestAccDbtCloudJobCostOptimizationFeatures test_step_number=1 test_terraform_path=/opt/homebrew/bin/terraform test_working_directory=/var/folders/hd/<sr>/T/plugintest3880779166
--- FAIL: TestAccDbtCloudJobCostOptimizationFeatures (3.90s)
    resource_acceptance_test.go:951: Step 1/3 error: Error running apply: exit status 1

        Error: Error creating job

          with dbtcloud_job.test_job,
          on terraform_plugin_test.tf line 23, in resource "dbtcloud_job" "test_job":
          23: resource "dbtcloud_job" "test_job" {

        Could not create job, unexpected error: unexpected status code 405:
        {"status":{"code":405,"is_success":false,"user_message":"node_selection are
        not valid
        features.","developer_message":""},"data":{},"extra":{},"error_code":null},
        URL: https://<host>/api/v2/accounts/<acc_id>/jobs/
FAIL
FAIL	github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/job	4.829s
?   	github.com/dbt-labs/terraform-provider-dbtcloud/pkg/framework/objects/job/validators	[no test files]
FAIL

Comment thread pkg/framework/objects/job/schema.go Outdated
Comment thread pkg/framework/objects/job/resource.go Outdated
trouze and others added 5 commits May 21, 2026 14:51
…O and deferral bugs (closes #664)

- Adds cost_optimization_features Set attribute as the preferred way to enable
  State-Aware Orchestration (SAO), replacing deprecated force_node_selection
- Fixes CI/Merge jobs erroring with SAO validation (405 error) by skipping
  SAO validation for CI and Merge job types
- Fixes CI/Merge jobs losing deferring_environment_id by preserving the value
  from the API response during Read
- Adds 5 new acceptance tests

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The dbt Cloud API uses force_node_selection (bool) under the hood, not a
cost_optimization_features array. Bridge in both directions:
- Write: cost_optimization_features=["node_selection"] -> force_node_selection=true
- Read: force_node_selection=true -> cost_optimization_features=["node_selection"]

Fixes inconsistent result after apply in TestAccDbtCloudJobCostOptimizationFeatures
and TestAccDbtCloudJobCIWithCostOptimizationFeatures.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…AO enforcement

- Fix Update path: use plan.ForceNodeSelection.IsNull() instead of
  job.ForceNodeSelection == nil for bridge condition, so clearing
  cost_optimization_features correctly sends force_node_selection=false
- Fix test step 2: account has account-level SAO enforcement so
  force_node_selection cannot be disabled; replace the clear step with
  a name-update step that verifies cost_optimization_features is stable

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ing cost_optimization_features

The previous bridge used the invented value "node_selection" for
cost_optimization_features and serialized that field to the dbt Cloud API.
Two real bugs fell out of this:

1. "node_selection" is not in CostOptimizationFeature in dbt-cloud
   (sinter/common/constants/jobs.py). The valid value is
   "state_aware_orchestration". On Fusion-capable accounts the API
   rejects the create with 405 "node_selection are not valid features."
   On non-Fusion accounts (dbt_version != "latest-fusion") the API
   silently drops the field, which is why this only failed for some
   reviewers but not the original author.

2. The Create/Update bridge mapped ["node_selection"] to
   force_node_selection = true. That is the inverse of what SAO means:
   force_node_selection = false is SAO enabled
   (≡ cost_optimization_features = ["state_aware_orchestration"]).
   See sinter/api/v2/views/jobs.py and sinter/mappers/job_definition.py
   in dbt-cloud. Any user opting into the new attribute was getting the
   opposite of the intended behavior.

This commit:

- Renames the only valid value to "state_aware_orchestration" in the
  schema description, bridge, Read-derivation, acceptance tests, and
  regenerated docs.
- Inverts the bridge so ["state_aware_orchestration"] →
  force_node_selection = false and an empty/unset set →
  force_node_selection = true.
- Marks Job.CostOptimizationFeatures as json:"-" so the provider only
  POSTs force_node_selection. The dbt Cloud API derives the new-style
  presentation from that bool, so we avoid double-writing contradictory
  fields and avoid 405s on Fusion accounts.
- Extends the CI/Merge SAO skip from Update into Create. The API
  rejects force_node_selection on CI/Merge job types; Create now
  infers the effective type from explicit job_type or triggers and
  drops SAO fields before calling the API.
- Removes the unreachable sliceStringToTypesSet helper that was
  flagged in review.
- Skips TestAccDbtCloudJobCostOptimizationFeatures with a clear note
  that it requires either account-level SAO enforcement or
  dbt_version = "latest-fusion" with SAO available. The default test
  account/version cannot satisfy either condition, so the test was
  failing for reviewers running the suite locally.
- Restores the dbt_version description that was inadvertently shortened.

Co-authored-by: Cursor <cursoragent@cursor.com>
@will-sargent-dbtlabs will-sargent-dbtlabs force-pushed the feat/issue-664-cost-optimization branch from 556f080 to 69cfd38 Compare May 21, 2026 21:52
@will-sargent-dbtlabs
Copy link
Copy Markdown
Contributor

will-sargent-dbtlabs commented May 21, 2026

@gravelbit Tests are pending, but give it a run locally. The test that failed for you works for me locally now, and was failing for me as well initially. Part of the local test failure was I believe that that test needed to be skipped depending on the combination of fusion/non-fusion and CI/Deploy.. or something along those lines. Give it a look-see now and see how we are doing.

I rebased it onto the latest main because it was 20 commits behind, to sort out conflicts.

CI on PR #670 caught a plan/apply consistency failure in
TestAccDbtCloudJobCIWithCostOptimizationFeatures:

  .cost_optimization_features: planned set element
  cty.StringVal("state_aware_orchestration") does not correlate
  with any element in actual.

The previous commit correctly skipped the SAO fields at the API
boundary for CI/Merge job types (to avoid the 405), but then still
overwrote plan.CostOptimizationFeatures from the API response. The
API doesn't store the field for those job types, so it derives back
to empty — but the user's plan still says ["state_aware_orchestration"],
which the framework flags as inconsistent.

Fix: for CI/Merge jobs, preserve whatever the user planned for
force_node_selection and cost_optimization_features rather than
deriving from the API response. The user's value is benign (we
never POST it for those job types) and keeps plan==apply consistent.
For all other job types the API response is still authoritative.

Apply the same logic in Read so that subsequent plans don't show
perpetual drift, with a fallback to empty/null on fresh imports so
ImportStateVerify still round-trips cleanly.

Verified locally with TF_ACC=1 against the dbt Cloud test account:
- TestAccDbtCloudJobCIWithCostOptimizationFeatures: PASS
- TestAccDbtCloudJobCIWithDeferringEnvironment: PASS
- TestAccDbtCloudJobMergeWithDeferringEnvironment: PASS
- TestAccDbtCloudJobCostOptimizationFeatures: SKIP (as designed)
- TestAccDbtCloudJobResource, JobCISettings, JobResourceIntervalCron,
  JobResourceJobTypeAndCompareChanges, JobResourceExecuteStepsValid: PASS

Co-authored-by: Cursor <cursoragent@cursor.com>
@will-sargent-dbtlabs
Copy link
Copy Markdown
Contributor

@gravelbit Looks like the CI is sorted. Let me know if your local run is working for you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add cost_optimization_features to dbtcloud_job + fix CI/Merge SAO and deferral bugs

4 participants