Skip to content

[FS2524] Add Feature Spec as a Bundle Resource - #7364

Merged
andrewnester merged 20 commits into
mainfrom
fs2524-feature-specs
Nov 11, 2026
Merged

andrewnester merged 20 commits into
mainfrom
fs2524-feature-specs

Conversation

@Daniel114510

@Daniel114510 Daniel114510 commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Changes

Add feature_specs as a bundle resource type, with a grants field for Unity Catalog privileges on the feature spec.

  • The Feature Engineering API has create, get and delete for feature specs and no update, and every field is IMMUTABLE. Any change therefore recreates the spec, and the adapter has no DoUpdate.
  • A feature spec is a UC function, so grants reuses the grants sub-resource with securable type function. When the spec is recreated, the bundle re-applies its declared grants to the new spec.
  • The fields rename_outputs, default_values and input_bindings of feature spec are compared by key. The string list fields exclude_columns and extra_pip_requirements.requirements are sorted in both the config and the read. This ensures reordering any of these order-free lists plans no change instead of a recreate.
  • bundle deploy and bundle destroy ask for approval before recreating or deleting a feature spec.

Why

Feature specs can only be created through the Python client or the API today. Supporting them in bundles is part of bringing the Feature Engineering API to Declarative Automation Bundles.

Tests

  • Acceptance tests:
    • bundle/resources/feature_specs/basic: create, no-op plan, recreate, destroy
    • bundle/resources/feature_specs/order: which list reorders recreate
    • bundle/resources/feature_specs/remote-delete: spec deleted outside the bundle
    • bundle/resources/grants/feature_specs: grants re-applied after recreate
    • bundle/deployment/bind/feature_spec: bind and unbind an existing spec
    • bundle/resource_deps/implicit_deps_feature_spec: deploys after its catalog and schema
    • bundle/invariant/configs/feature_spec.yml.tmpl: no drift, repeatable delete and destroy
    • bundle/python/feature_specs-support: specs defined and changed in Python
    • experimental/open: lists feature_specs
  • Unit tests:
    • bundle/direct/dresources/feature_spec_test.go: list sorting and matching
    • resource registration tests: feature specs wired like other resources

Daniel114510 and others added 7 commits November 4, 2026 19:24
Dropped when rebasing onto the bot's Update Go SDK to v0.194.0 PR.

Co-authored-by: Isaac <no-reply@databricks.com>
A feature spec has create, get and delete and no update, and every field is
immutable, so any change recreates it. It is a UC function, so its grants go
through the existing grants sub-resource with securable type "function"; on a
recreate the grants node plans an update and re-applies them.

The map-like pair lists (rename_outputs, default_values, input_bindings) are
compared by key, and exclude_columns and the extra pip requirements are sorted
on both sides, so reordering them is not a recreate. features and the lookup
keys stay positional: their order sets the output columns and must match the
table's primary key.

Co-authored-by: Isaac <no-reply@databricks.com>
… FeatureSpec

Replaced by the generator after rebasing onto the SDK bump.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Also give the fake's feature spec map its own block and shorten the
recreate prompt.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@github-actions github-actions Bot added DABs DABs related issues PyDABs labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Commit: f34d1a8e2 | Workflow run

Regression test report

Selected 14 changed tests (limit=20, 0 not selected)

21 tests: regression=14 unreleased=0 coverage=7 failing=0
test branch main (6bdf874) latest (v1.25.0)
TestAccept/bundle/deployment/bind/feature_spec/DMS= ✅ ❌ ❌
TestAccept/bundle/deployment/bind/feature_spec/DMS=true ✅ ❌ ❌
TestAccept/bundle/invariant/continue_293/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl ✅ ✅ ✅
TestAccept/bundle/invariant/delete_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN= ✅ ✅ ✅
TestAccept/bundle/invariant/delete_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN=1 ✅ ✅ ✅
TestAccept/bundle/invariant/destroy_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN= ✅ ✅ ✅
TestAccept/bundle/invariant/destroy_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN=1 ✅ ✅ ✅
TestAccept/bundle/invariant/no_drift/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN= ✅ ✅ ✅
TestAccept/bundle/invariant/no_drift/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN=1 ✅ ✅ ✅
TestAccept/bundle/python/feature_specs-support/DMS=/PYDAB_VERSION=current ✅ ❌ ❌
TestAccept/bundle/python/feature_specs-support/DMS=true/PYDAB_VERSION=current ✅ ❌ ❌
TestAccept/bundle/refschema/DMS= ✅ ❌ ❌
TestAccept/bundle/refschema/DMS=true ✅ ❌ ❌
TestAccept/bundle/resource_deps/implicit_deps_feature_spec/DMS= ✅ ❌ ❌
TestAccept/bundle/resource_deps/implicit_deps_feature_spec/DMS=true ✅ ❌ ❌
TestAccept/bundle/resources/feature_specs/basic/DMS= ✅ ❌ ❌
TestAccept/bundle/resources/feature_specs/order/DMS= ✅ ❌ ❌
TestAccept/bundle/resources/feature_specs/remote-delete/DMS= ✅ ❌ ❌
TestAccept/bundle/resources/feature_specs/uppercase-name/DMS= ✅ ❌ ❌
TestAccept/bundle/resources/grants/feature_specs/DMS= ✅ ❌ ❌
TestAccept/experimental/open ✅ ❌ ❌
TestAccept/bundle/deployment/bind/feature_spec/DMS=: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: Diff:
        --- bundle/deployment/bind/feature_spec/output.txt
        +++ /tmp/TestAcceptbundledeploymentbindfeature_specDMS=3987272713/002/output.txt
        @@ -16,38 +16,10 @@
         }
         
         >>> [CLI] bundle deployment bind spec1 main.fspec_[UNIQUE_NAME].boundspec --auto-approve
        -Successfully bound feature_spec with an id 'main.fspec_[UNIQUE_NAME].boundspec'
        -Run 'bundle deploy' to deploy changes to your workspace
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
         
        ->>> [CLI] bundle summary
        -Name: test-bind-feature-spec-[UNIQUE_NAME]
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/test-bind-feature-spec-[UNIQUE_NAME]/default
        -Resources:
        -  Feature Specs:
        -    spec1:
        -      Name: main.fspec_[UNIQUE_NAME].boundspec
        -      URL:  [DATABRICKS_URL]/explore/data/functions/main/fspec_[UNIQUE_NAME]/boundspec
        +Error: no such resource: spec1
         
        -=== The bound feature spec matches the config, so there is nothing to change
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged
        -
        ->>> [CLI] bundle deployment unbind spec1
        -
        ->>> [CLI] bundle summary
        -Name: test-bind-feature-spec-[UNIQUE_NAME]
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/test-bind-feature-spec-[UNIQUE_NAME]/default
        -Resources:
        -  Feature Specs:
        -    spec1:
        -      Name: main.fspec_[UNIQUE_NAME].boundspec
        -      URL:  (not deployed)
        -
        ->>> [CLI] api delete /api/2.0/feature-engineering/feature-specs/main.fspec_[UNIQUE_NAME].boundspec
        -{}
        +Exit code: 1
        
    acceptance_test.go:1123: 
        LOG.cleanup: Warning: unknown field: feature_specs
        LOG.cleanup:   at resources
        LOG.cleanup:   in databricks.yml:5:3

latest (v1.25.0): same output as main (6bdf874)

TestAccept/bundle/deployment/bind/feature_spec/DMS=true: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: Diff:
        --- bundle/deployment/bind/feature_spec/output.txt
        +++ /tmp/TestAcceptbundledeploymentbindfeature_specDMS=true1360455064/002/output.txt
        @@ -16,38 +16,10 @@
         }
         
         >>> [CLI] bundle deployment bind spec1 main.fspec_[UNIQUE_NAME].boundspec --auto-approve
        -Successfully bound feature_spec with an id 'main.fspec_[UNIQUE_NAME].boundspec'
        -Run 'bundle deploy' to deploy changes to your workspace
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
         
        ->>> [CLI] bundle summary
        -Name: test-bind-feature-spec-[UNIQUE_NAME]
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/test-bind-feature-spec-[UNIQUE_NAME]/default
        -Resources:
        -  Feature Specs:
        -    spec1:
        -      Name: main.fspec_[UNIQUE_NAME].boundspec
        -      URL:  [DATABRICKS_URL]/explore/data/functions/main/fspec_[UNIQUE_NAME]/boundspec
        +Error: no such resource: spec1
         
        -=== The bound feature spec matches the config, so there is nothing to change
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged
        -
        ->>> [CLI] bundle deployment unbind spec1
        -
        ->>> [CLI] bundle summary
        -Name: test-bind-feature-spec-[UNIQUE_NAME]
        -Target: default
        -Workspace:
        -  User: [USERNAME]
        -  Path: /Workspace/Users/[USERNAME]/.bundle/test-bind-feature-spec-[UNIQUE_NAME]/default
        -Resources:
        -  Feature Specs:
        -    spec1:
        -      Name: main.fspec_[UNIQUE_NAME].boundspec
        -      URL:  (not deployed)
        -
        ->>> [CLI] api delete /api/2.0/feature-engineering/feature-specs/main.fspec_[UNIQUE_NAME].boundspec
        -{}
        +Exit code: 1
        
    acceptance_test.go:1123: 
        LOG.cleanup: Warning: unknown field: feature_specs
        LOG.cleanup:   at resources
        LOG.cleanup:   in databricks.yml:5:3

latest (v1.25.0): same output as main (6bdf874)

TestAccept/bundle/python/feature_specs-support/DMS=/PYDAB_VERSION=current: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: Diff:
        --- bundle/python/feature_specs-support/output.txt
        +++ /tmp/TestAcceptbundlepythonfeature_specs-supportDMS=PYDAB_VERSIO1867803670/002/output.txt
        @@ -1,5 +1,13 @@
         
         >>> uv run [UV_ARGS] -q [CLI] bundle validate --output json
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:13:3
        +
        +Error: unknown field: feature_specs
        +  at resources
        +  in __generated_by_python__.yml:1:351
        +
         {
           "experimental": {
             "python": {
        @@ -11,47 +19,7 @@
               ]
             }
           },
        -  "resources": {
        -    "feature_specs": {
        -      "my_feature_spec_1": {
        -        "features": [
        -          {
        -            "feature_lookup": {
        -              "feature_names": [
        -                "age"
        -              ],
        -              "lookup_keys": [
        -                "user_id"
        -              ],
        -              "table": "main.default.user_features"
        -            }
        -          }
        -        ],
        -        "grants": [
        -          {
        -            "principal": "account users",
        -            "privileges": [
        -              "EXECUTE"
        -            ]
        -          }
        -        ],
        -        "name": "main.default.my_feature_spec_1_updated"
        -      },
        -      "my_feature_spec_2": {
        -        "extra_pip_requirements": {
        -          "requirements": [
        -            "numpy==2.1.0"
        -          ]
        -        },
        -        "features": [
        -          {
        -            "declarative_feature": {
        -              "feature": "main.default.my_feature"
        -            }
        -          }
        -        ],
        -        "name": "main.default.my_feature_spec_2_updated"
        -      }
        -    }
        -  }
        +  "resources": {}
         }
        +
        +Exit code: 1

latest (v1.25.0): same output as main (6bdf874)

TestAccept/bundle/python/feature_specs-support/DMS=true/PYDAB_VERSION=current: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: Diff:
        --- bundle/python/feature_specs-support/output.txt
        +++ /tmp/TestAcceptbundlepythonfeature_specs-supportDMS=truePYDAB_VE1583215511/002/output.txt
        @@ -1,5 +1,13 @@
         
         >>> uv run [UV_ARGS] -q [CLI] bundle validate --output json
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:13:3
        +
        +Error: unknown field: feature_specs
        +  at resources
        +  in __generated_by_python__.yml:1:351
        +
         {
           "experimental": {
             "python": {
        @@ -11,47 +19,7 @@
               ]
             }
           },
        -  "resources": {
        -    "feature_specs": {
        -      "my_feature_spec_1": {
        -        "features": [
        -          {
        -            "feature_lookup": {
        -              "feature_names": [
        -                "age"
        -              ],
        -              "lookup_keys": [
        -                "user_id"
        -              ],
        -              "table": "main.default.user_features"
        -            }
        -          }
        -        ],
        -        "grants": [
        -          {
        -            "principal": "account users",
        -            "privileges": [
        -              "EXECUTE"
        -            ]
        -          }
        -        ],
        -        "name": "main.default.my_feature_spec_1_updated"
        -      },
        -      "my_feature_spec_2": {
        -        "extra_pip_requirements": {
        -          "requirements": [
        -            "numpy==2.1.0"
        -          ]
        -        },
        -        "features": [
        -          {
        -            "declarative_feature": {
        -              "feature": "main.default.my_feature"
        -            }
        -          }
        -        ],
        -        "name": "main.default.my_feature_spec_2_updated"
        -      }
        -    }
        -  }
        +  "resources": {}
         }
        +
        +Exit code: 1

latest (v1.25.0): same output as main (6bdf874)

TestAccept/bundle/refschema/DMS=: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1316: Missing output file: output/feature_specs.txt

latest (v1.25.0):

acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/features.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/features.txt
        @@ -100,7 +100,6 @@
         resources.features.*.lineage_context.notebook_id	int64	ALL
         resources.features.*.modified_status	string	INPUT
         resources.features.*.name	string	ALL
        -resources.features.*.output_type_text	string	ALL
         resources.features.*.schema_name	string	ALL
         resources.features.*.source	ml.DataSource	ALL
         resources.features.*.source.delta_table_source	*ml.DeltaTableSource	ALL
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/materialization_sets.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/materialization_sets.txt
        @@ -12,9 +12,7 @@
         resources.materialization_sets.*.materialized_features[*].cron_schedule_trigger	*ml.CronSchedule	ALL
         resources.materialization_sets.*.materialized_features[*].cron_schedule_trigger.cron_expression	string	ALL
         resources.materialization_sets.*.materialized_features[*].cron_schedule_trigger.mode	ml.CronScheduleMode	ALL
        -resources.materialization_sets.*.materialized_features[*].cron_schedule_trigger.paused	bool	ALL
         resources.materialization_sets.*.materialized_features[*].cron_schedule_trigger.timezone_id	string	ALL
        -resources.materialization_sets.*.materialized_features[*].external_trigger	*ml.ExternalTrigger	ALL
         resources.materialization_sets.*.materialized_features[*].feature_name	string	ALL
         resources.materialization_sets.*.materialized_features[*].is_online	bool	ALL
         resources.materialization_sets.*.materialized_features[*].job_id	int64	ALL
        @@ -53,7 +50,6 @@
         resources.materialization_sets.*.materialized_features[*].table_trigger	*ml.TableTrigger	ALL
         resources.materialization_sets.*.materialized_features[*].table_trigger.condition	ml.TableTriggerTableTriggerCondition	ALL
         resources.materialization_sets.*.materialized_features[*].table_trigger.min_time_between_triggers	*duration.Duration	ALL
        -resources.materialization_sets.*.materialized_features[*].table_trigger.paused	bool	ALL
         resources.materialization_sets.*.materialized_features[*].table_trigger.wait_after_last_change	*duration.Duration	ALL
         resources.materialization_sets.*.materialized_features[*].tags	map[string]string	ALL
         resources.materialization_sets.*.materialized_features[*].tags.*	string	ALL
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/postgres_databases.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/postgres_databases.txt
        @@ -10,7 +10,7 @@
         resources.postgres_databases.*.name	string	REMOTE
         resources.postgres_databases.*.parent	string	ALL
         resources.postgres_databases.*.postgres_database	string	ALL
        -resources.postgres_databases.*.replace_existing	*bool	INPUT	STATE
        +resources.postgres_databases.*.replace_existing	bool	INPUT	STATE
         resources.postgres_databases.*.role	string	ALL
         resources.postgres_databases.*.status	*postgres.DatabaseDatabaseStatus	REMOTE
         resources.postgres_databases.*.status.database_id	string	REMOTE
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/postgres_roles.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/postgres_roles.txt
        @@ -17,7 +17,7 @@
         resources.postgres_roles.*.name	string	REMOTE
         resources.postgres_roles.*.parent	string	ALL
         resources.postgres_roles.*.postgres_role	string	ALL
        -resources.postgres_roles.*.replace_existing	*bool	INPUT	STATE
        +resources.postgres_roles.*.replace_existing	bool	INPUT	STATE
         resources.postgres_roles.*.role_id	string	ALL
         resources.postgres_roles.*.status	*postgres.RoleRoleStatus	REMOTE
         resources.postgres_roles.*.status.attributes	*postgres.RoleAttributes	REMOTE
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/vector_search_endpoints.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/vector_search_endpoints.txt
        @@ -23,13 +23,8 @@
         resources.vector_search_endpoints.*.name	string	ALL
         resources.vector_search_endpoints.*.num_indexes	int	REMOTE
         resources.vector_search_endpoints.*.scaling_info	*vectorsearch.EndpointScalingInfo	REMOTE
        -resources.vector_search_endpoints.*.scaling_info.delayed_reason	vectorsearch.EndpointScalingInfoScalingDelayReason	REMOTE
        -resources.vector_search_endpoints.*.scaling_info.estimated_completion_timestamp	int64	REMOTE
        -resources.vector_search_endpoints.*.scaling_info.index_initialization_percentage	int	REMOTE
        -resources.vector_search_endpoints.*.scaling_info.phase	vectorsearch.EndpointScalingInfoScalingPhase	REMOTE
         resources.vector_search_endpoints.*.scaling_info.requested_target_qps	int64	REMOTE
         resources.vector_search_endpoints.*.scaling_info.state	vectorsearch.ScalingChangeState	REMOTE
        -resources.vector_search_endpoints.*.scaling_info.target_qps_change_allowed_after_timestamp	int64	REMOTE
         resources.vector_search_endpoints.*.target_qps	int64	ALL
         resources.vector_search_endpoints.*.url	string	INPUT
         resources.vector_search_endpoints.*.usage_policy_id	string	INPUT	STATE
        
    acceptance_test.go:1316: Missing output file: output/feature_specs.txt
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/postgres_projects.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/postgres_projects.txt
        @@ -35,7 +35,7 @@
         resources.postgres_projects.*.name	string	REMOTE
         resources.postgres_projects.*.pg_version	int	ALL
         resources.postgres_projects.*.project_id	string	ALL
        -resources.postgres_projects.*.purge_on_delete	*bool	INPUT	STATE
        +resources.postgres_projects.*.purge_on_delete	bool	INPUT	STATE
         resources.postgres_projects.*.purge_time	*time.Time	REMOTE
         resources.postgres_projects.*.status	*postgres.ProjectStatus	REMOTE
         resources.postgres_projects.*.status.branch_logical_size_limit_bytes	int64	REMOTE
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/postgres_branches.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/postgres_branches.txt
        @@ -12,8 +12,8 @@
         resources.postgres_branches.*.name	string	REMOTE
         resources.postgres_branches.*.no_expiry	bool	ALL
         resources.postgres_branches.*.parent	string	ALL
        -resources.postgres_branches.*.purge_on_delete	*bool	INPUT	STATE
        -resources.postgres_branches.*.replace_existing	*bool	INPUT	STATE
        +resources.postgres_branches.*.purge_on_delete	bool	INPUT	STATE
        +resources.postgres_branches.*.replace_existing	bool	INPUT	STATE
         resources.postgres_branches.*.source_branch	string	ALL
         resources.postgres_branches.*.source_branch_lsn	string	ALL
         resources.postgres_branches.*.source_branch_time	*time.Time	ALL
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/postgres_endpoints.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/postgres_endpoints.txt
        @@ -18,7 +18,7 @@
         resources.postgres_endpoints.*.name	string	REMOTE
         resources.postgres_endpoints.*.no_suspension	bool	ALL
         resources.postgres_endpoints.*.parent	string	ALL
        -resources.postgres_endpoints.*.replace_existing	*bool	INPUT	STATE
        +resources.postgres_endpoints.*.replace_existing	bool	INPUT	STATE
         resources.postgres_endpoints.*.settings	*postgres.EndpointSettings	ALL
         resources.postgres_endpoints.*.settings.pg_settings	map[string]string	ALL
         resources.postgres_endpoints.*.settings.pg_settings.*	string	ALL
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/root.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=1465925618/001/output/root.txt
        @@ -9,7 +9,7 @@
         artifacts	config.Artifacts	INPUT
         artifacts.*	*config.Artifact	INPUT
         artifacts.*.build	string	INPUT
        -artifacts.*.dynamic_version	*bool	INPUT
        +artifacts.*.dynamic_version	bool	INPUT
         artifacts.*.executable	exec.ExecutableType	INPUT
         artifacts.*.files	[]config.ArtifactFile	INPUT
         artifacts.*.files[*]	config.ArtifactFile	INPUT
        @@ -29,11 +29,11 @@
         bundle.databricks_cli_version	string	INPUT
         bundle.deployment	config.Deployment	INPUT
         bundle.deployment.deployment_id	string	INPUT
        -bundle.deployment.fail_on_active_runs	*bool	INPUT
        +bundle.deployment.fail_on_active_runs	bool	INPUT
         bundle.deployment.latest_version_id	int	INPUT
         bundle.deployment.lock	config.Lock	INPUT
         bundle.deployment.lock.enabled	*bool	INPUT
        -bundle.deployment.lock.force	*bool	INPUT
        +bundle.deployment.lock.force	bool	INPUT
         bundle.engine	engine.EngineType	INPUT
         bundle.environment	string	INPUT
         bundle.force	bool	INPUT
        @@ -55,7 +52,7 @@
         environments.*.artifacts	config.Artifacts	INPUT
         environments.*.artifacts.*	*config.Artifact	INPUT
         environments.*.artifacts.*.build	string	INPUT
        -environments.*.artifacts.*.dynamic_version	*bool	INPUT
        +environments.*.artifacts.*.dynamic_version	bool	INPUT
         environments.*.artifacts.*.executable	exec.ExecutableType	INPUT
         environments.*.artifacts.*.files	[]config.ArtifactFile	INPUT
         environments.*.artifacts.*.files[*]	config.ArtifactFile	INPUT
        @@ -75,11 +72,11 @@
         environments.*.bundle.databricks_cli_version	string	INPUT
         environments.*.bundle.deployment	config.Deployment	INPUT
         environments.*.bundle.deployment.deployment_id	string	INPUT
        -environments.*.bundle.deployment.fail_on_active_runs	*bool	INPUT
        +environments.*.bundle.deployment.fail_on_active_runs	bool	INPUT
         environments.*.bundle.deployment.latest_version_id	int	INPUT
         environments.*.bundle.deployment.lock	config.Lock	INPUT
         environments.*.bundle.deployment.lock.enabled	*bool	INPUT
        -environments.*.bundle.deployment.lock.force	*bool	INPUT
        +environments.*.bundle.deployment.lock.force	bool	INPUT
         environments.*.bundle.engine	engine.EngineType	INPUT
         environments.*.bundle.environment	string	INPUT
         environments.*.bundle.force	bool	INPUT
        @@ -97,7 +91,7 @@
         environments.*.bundle.uuid	string	INPUT
         environments.*.cluster_id	string	INPUT
         environments.*.compute_id	string	INPUT
        -environments.*.default	*bool	INPUT
        +environments.*.default	bool	INPUT
         environments.*.git	config.Git	INPUT
         environments.*.git.actual_branch	string	INPUT
         environments.*.git.branch	string	INPUT
        @@ -148,7 +142,7 @@
         environments.*.variables.*.lookup.query	string	INPUT
         environments.*.variables.*.lookup.service_principal	string	INPUT
         environments.*.variables.*.lookup.warehouse	string	INPUT
        -environments.*.variables.*.sensitive	*bool	INPUT
        +environments.*.variables.*.sensitive	bool	INPUT
         environments.*.variables.*.type	variable.VariableType	INPUT
         environments.*.variables.*.value	any	INPUT
         environments.*.workspace	*config.Workspace	INPUT
        @@ -159,7 +153,7 @@
         environments.*.workspace.azure_environment	string	INPUT
         environments.*.workspace.azure_login_app_id	string	INPUT
         environments.*.workspace.azure_tenant_id	string	INPUT
        -environments.*.workspace.azure_use_msi	*bool	INPUT
        +environments.*.workspace.azure_use_msi	bool	INPUT
         environments.*.workspace.azure_workspace_resource_id	string	INPUT
         environments.*.workspace.client_id	string	INPUT
         environments.*.workspace.current_user	*config.User	INPUT
        @@ -204,7 +198,7 @@
         environments.*.workspace.current_user.schemas[*]	iam.UserSchema	INPUT
         environments.*.workspace.current_user.short_name	string	INPUT
         environments.*.workspace.current_user.userName	string	INPUT
... (117 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)
TestAccept/bundle/refschema/DMS=true: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1316: Missing output file: output/feature_specs.txt

latest (v1.25.0):

acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/postgres_projects.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=true2505987268/001/output/postgres_projects.txt
        @@ -35,7 +35,7 @@
         resources.postgres_projects.*.name	string	REMOTE
         resources.postgres_projects.*.pg_version	int	ALL
         resources.postgres_projects.*.project_id	string	ALL
        -resources.postgres_projects.*.purge_on_delete	*bool	INPUT	STATE
        +resources.postgres_projects.*.purge_on_delete	bool	INPUT	STATE
         resources.postgres_projects.*.purge_time	*time.Time	REMOTE
         resources.postgres_projects.*.status	*postgres.ProjectStatus	REMOTE
         resources.postgres_projects.*.status.branch_logical_size_limit_bytes	int64	REMOTE
        
    acceptance_test.go:1345: Diff:
        --- bundle/refschema/output/root.txt
        +++ /tmp/TestAcceptbundlerefschemaDMS=true2505987268/001/output/root.txt
        @@ -9,7 +9,7 @@
         artifacts	config.Artifacts	INPUT
         artifacts.*	*config.Artifact	INPUT
         artifacts.*.build	string	INPUT
        -artifacts.*.dynamic_version	*bool	INPUT
        +artifacts.*.dynamic_version	bool	INPUT
         artifacts.*.executable	exec.ExecutableType	INPUT
         artifacts.*.files	[]config.ArtifactFile	INPUT
         artifacts.*.files[*]	config.ArtifactFile	INPUT
        @@ -29,11 +29,11 @@
         bundle.databricks_cli_version	string	INPUT
         bundle.deployment	config.Deployment	INPUT
         bundle.deployment.deployment_id	string	INPUT
        -bundle.deployment.fail_on_active_runs	*bool	INPUT
        +bundle.deployment.fail_on_active_runs	bool	INPUT
         bundle.deployment.latest_version_id	int	INPUT
         bundle.deployment.lock	config.Lock	INPUT
         bundle.deployment.lock.enabled	*bool	INPUT
        -bundle.deployment.lock.force	*bool	INPUT
        +bundle.deployment.lock.force	bool	INPUT
         bundle.engine	engine.EngineType	INPUT
         bundle.environment	string	INPUT
         bundle.force	bool	INPUT
        @@ -55,7 +52,7 @@
         environments.*.artifacts	config.Artifacts	INPUT
         environments.*.artifacts.*	*config.Artifact	INPUT
         environments.*.artifacts.*.build	string	INPUT
        -environments.*.artifacts.*.dynamic_version	*bool	INPUT
        +environments.*.artifacts.*.dynamic_version	bool	INPUT
         environments.*.artifacts.*.executable	exec.ExecutableType	INPUT
         environments.*.artifacts.*.files	[]config.ArtifactFile	INPUT
         environments.*.artifacts.*.files[*]	config.ArtifactFile	INPUT
        @@ -75,11 +72,11 @@
         environments.*.bundle.databricks_cli_version	string	INPUT
         environments.*.bundle.deployment	config.Deployment	INPUT
         environments.*.bundle.deployment.deployment_id	string	INPUT
        -environments.*.bundle.deployment.fail_on_active_runs	*bool	INPUT
        +environments.*.bundle.deployment.fail_on_active_runs	bool	INPUT
         environments.*.bundle.deployment.latest_version_id	int	INPUT
         environments.*.bundle.deployment.lock	config.Lock	INPUT
         environments.*.bundle.deployment.lock.enabled	*bool	INPUT
        -environments.*.bundle.deployment.lock.force	*bool	INPUT
        +environments.*.bundle.deployment.lock.force	bool	INPUT
         environments.*.bundle.engine	engine.EngineType	INPUT
         environments.*.bundle.environment	string	INPUT
         environments.*.bundle.force	bool	INPUT
        @@ -97,7 +91,7 @@
         environments.*.bundle.uuid	string	INPUT
         environments.*.cluster_id	string	INPUT
         environments.*.compute_id	string	INPUT
        -environments.*.default	*bool	INPUT
        +environments.*.default	bool	INPUT
         environments.*.git	config.Git	INPUT
         environments.*.git.actual_branch	string	INPUT
         environments.*.git.branch	string	INPUT
        @@ -148,7 +142,7 @@
         environments.*.variables.*.lookup.query	string	INPUT
         environments.*.variables.*.lookup.service_principal	string	INPUT
         environments.*.variables.*.lookup.warehouse	string	INPUT
        -environments.*.variables.*.sensitive	*bool	INPUT
        +environments.*.variables.*.sensitive	bool	INPUT
         environments.*.variables.*.type	variable.VariableType	INPUT
         environments.*.variables.*.value	any	INPUT
         environments.*.workspace	*config.Workspace	INPUT
        @@ -159,7 +153,7 @@
         environments.*.workspace.azure_environment	string	INPUT
         environments.*.workspace.azure_login_app_id	string	INPUT
         environments.*.workspace.azure_tenant_id	string	INPUT
        -environments.*.workspace.azure_use_msi	*bool	INPUT
        +environments.*.workspace.azure_use_msi	bool	INPUT
         environments.*.workspace.azure_workspace_resource_id	string	INPUT
         environments.*.workspace.client_id	string	INPUT
         environments.*.workspace.current_user	*config.User	INPUT
        @@ -204,7 +198,7 @@
         environments.*.workspace.current_user.schemas[*]	iam.UserSchema	INPUT
         environments.*.workspace.current_user.short_name	string	INPUT
         environments.*.workspace.current_user.userName	string	INPUT
        -environments.*.workspace.experimental_is_unified_host	*bool	INPUT
        +environments.*.workspace.experimental_is_unified_host	bool	INPUT
         environments.*.workspace.file_path	string	INPUT
         environments.*.workspace.google_service_account	string	INPUT
         environments.*.workspace.host	string	INPUT
        @@ -217,19 +211,19 @@
         environments.*.workspace.workspace_id	string	INPUT
         experimental	*config.Experimental	INPUT
         experimental.deployment_history	*bool	INPUT
        -experimental.immutable_folder	*bool	INPUT
        +experimental.immutable_folder	bool	INPUT
         experimental.pydabs	config.PyDABs	INPUT
        -experimental.pydabs.enabled	*bool	INPUT
        +experimental.pydabs.enabled	bool	INPUT
         experimental.python	config.Python	INPUT
         experimental.python.mutators	[]string	INPUT
         experimental.python.mutators[*]	string	INPUT
         experimental.python.venv_path	string	INPUT
        -experimental.python_wheel_wrapper	*bool	INPUT
        +experimental.python_wheel_wrapper	bool	INPUT
         experimental.scripts	map[config.ScriptHook]config.Command	INPUT
         experimental.scripts.*	config.Command	INPUT
        -experimental.skip_artifact_cleanup	*bool	INPUT
        -experimental.skip_name_prefix_for_schema	*bool	INPUT
        -experimental.use_legacy_run_as	*bool	INPUT
        +experimental.skip_artifact_cleanup	bool	INPUT
        +experimental.skip_name_prefix_for_schema	bool	INPUT
        +experimental.use_legacy_run_as	bool	INPUT
         include	[]string	INPUT
         include[*]	string	INPUT
         permissions	[]resources.Permission	INPUT
        @@ -272,7 +259,7 @@
         targets.*.artifacts	config.Artifacts	INPUT
         targets.*.artifacts.*	*config.Artifact	INPUT
         targets.*.artifacts.*.build	string	INPUT
        -targets.*.artifacts.*.dynamic_version	*bool	INPUT
        +targets.*.artifacts.*.dynamic_version	bool	INPUT
         targets.*.artifacts.*.executable	exec.ExecutableType	INPUT
         targets.*.artifacts.*.files	[]config.ArtifactFile	INPUT
         targets.*.artifacts.*.files[*]	config.ArtifactFile	INPUT
        @@ -292,11 +279,11 @@
         targets.*.bundle.databricks_cli_version	string	INPUT
         targets.*.bundle.deployment	config.Deployment	INPUT
         targets.*.bundle.deployment.deployment_id	string	INPUT
        -targets.*.bundle.deployment.fail_on_active_runs	*bool	INPUT
        +targets.*.bundle.deployment.fail_on_active_runs	bool	INPUT
         targets.*.bundle.deployment.latest_version_id	int	INPUT
         targets.*.bundle.deployment.lock	config.Lock	INPUT
         targets.*.bundle.deployment.lock.enabled	*bool	INPUT
        -targets.*.bundle.deployment.lock.force	*bool	INPUT
        +targets.*.bundle.deployment.lock.force	bool	INPUT
         targets.*.bundle.engine	engine.EngineType	INPUT
         targets.*.bundle.environment	string	INPUT
         targets.*.bundle.force	bool	INPUT
        @@ -314,7 +298,7 @@
         targets.*.bundle.uuid	string	INPUT
         targets.*.cluster_id	string	INPUT
         targets.*.compute_id	string	INPUT
        -targets.*.default	*bool	INPUT
        +targets.*.default	bool	INPUT
         targets.*.git	config.Git	INPUT
         targets.*.git.actual_branch	string	INPUT
         targets.*.git.branch	string	INPUT
        @@ -365,7 +349,7 @@
         targets.*.variables.*.lookup.query	string	INPUT
         targets.*.variables.*.lookup.service_principal	string	INPUT
         targets.*.variables.*.lookup.warehouse	string	INPUT
        -targets.*.variables.*.sensitive	*bool	INPUT
        +targets.*.variables.*.sensitive	bool	INPUT
         targets.*.variables.*.type	variable.VariableType	INPUT
         targets.*.variables.*.value	any	INPUT
         targets.*.workspace	*config.Workspace	INPUT
        @@ -376,7 +360,7 @@
         targets.*.workspace.azure_environment	string	INPUT
         targets.*.workspace.azure_login_app_id	string	INPUT
         targets.*.workspace.azure_tenant_id	string	INPUT
        -targets.*.workspace.azure_use_msi	*bool	INPUT
        +targets.*.workspace.azure_use_msi	bool	INPUT
         targets.*.workspace.azure_workspace_resource_id	string	INPUT
         targets.*.workspace.client_id	string	INPUT
         targets.*.workspace.current_user	*config.User	INPUT
        @@ -421,7 +405,7 @@
         targets.*.workspace.current_user.schemas[*]	iam.UserSchema	INPUT
         targets.*.workspace.current_user.short_name	string	INPUT
         targets.*.workspace.current_user.userName	string	INPUT
        -targets.*.workspace.experimental_is_unified_host	*bool	INPUT
        +targets.*.workspace.experimental_is_unified_host	bool	INPUT
         targets.*.workspace.file_path	string	INPUT
         targets.*.workspace.google_service_account	string	INPUT
         targets.*.workspace.host	string	INPUT
        @@ -449,7 +433,7 @@
         variables.*.lookup.query	string	INPUT
         variables.*.lookup.service_principal	string	INPUT
         variables.*.lookup.warehouse	string	INPUT
        -variables.*.sensitive	*bool	INPUT
        +variables.*.sensitive	bool	INPUT
         variables.*.type	variable.VariableType	INPUT
         variables.*.value	any	INPUT
         workspace	config.Workspace	INPUT
        @@ -460,7 +444,7 @@
         workspace.azure_environment	string	INPUT
         workspace.azure_login_app_id	string	INPUT
         workspace.azure_tenant_id	string	INPUT
        -workspace.azure_use_msi	*bool	INPUT
        +workspace.azure_use_msi	bool	INPUT
         workspace.azure_workspace_resource_id	string	INPUT
         workspace.client_id	string	INPUT
... (117 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)
TestAccept/bundle/resource_deps/implicit_deps_feature_spec/DMS=: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n>>> [CLI] bundle validate -t dev -o json\n{\n  \"catalogs\": {\n    \"my_catalog\": {\n      \"name\": \"mycatalog\"\n    }\n  },\n  \"feature_specs\": {\n    \"my_feature_spec\": {\n      \"features\": [\n        {\n          \"feature_lookup\": {\n            \"feature_names\": [\n              \"age\"\n            ],\n            \"lookup_keys\": [\n              \"user_id\"\n            ],\n            \"table\": \"mycatalog.myschema.user_features\"\n          }\n        }\n      ],\n      \"name\": \"${resources.catalogs.my_catalog.name}.${resources.schemas.my_schema.name}.myspec\"\n    }\n  },\n  \"schemas\": {\n    \"my_schema\": {\n      \"catalog_name\": \"${resources.catalogs.my_catalog.name}\",\n      \"name\": \"dev_[USERNAME]_myschema\"\n    }\n  }\n}\n"
        	            	actual  : "\n>>> [CLI] bundle validate -t dev -o json\nWarning: unknown field: feature_specs\n  at resources\n  in databricks.yml:14:3\n\n{\n  \"catalogs\": {\n    \"my_catalog\": {\n      \"name\": \"mycatalog\"\n    }\n  },\n  \"schemas\": {\n    \"my_schema\": {\n      \"catalog_name\": \"${resources.catalogs.my_catalog.name}\",\n      \"name\": \"dev_[USERNAME]_myschema\"\n    }\n  }\n}\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -2,2 +2,6 @@
        	            	 >>> [CLI] bundle validate -t dev -o json
        	            	+Warning: unknown field: feature_specs
        	            	+  at resources
        	            	+  in databricks.yml:14:3
        	            	+
        	            	 {
        	            	@@ -6,20 +10,2 @@
        	            	       "name": "mycatalog"
        	            	-    }
        	            	-  },
        	            	-  "feature_specs": {
        	            	-    "my_feature_spec": {
        	            	-      "features": [
        	            	-        {
        	            	-          "feature_lookup": {
        	            	-            "feature_names": [
        	            	-              "age"
        	            	-            ],
        	            	-            "lookup_keys": [
        	            	-              "user_id"
        	            	-            ],
        	            	-            "table": "mycatalog.myschema.user_features"
        	            	-          }
        	            	-        }
        	            	-      ],
        	            	-      "name": "${resources.catalogs.my_catalog.name}.${resources.schemas.my_schema.name}.myspec"
        	            	     }
        	Test:       	TestAccept/bundle/resource_deps/implicit_deps_feature_spec/DMS=
        	Messages:   	bundle/resource_deps/implicit_deps_feature_spec/output.txt vs /tmp/TestAcceptbundleresource_depsimplicit_deps_feature_specDMS=1987229750/002/output.txt

latest (v1.25.0): same output as main (6bdf874)

TestAccept/bundle/resource_deps/implicit_deps_feature_spec/DMS=true: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: 
        	Error Trace:	/home/runner/work/cli/cli/libs/testdiff/testdiff.go:22
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1345
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:1086
        	            				/home/runner/work/cli/cli/acceptance/acceptance_test.go:660
        	Error:      	Not equal: 
        	            	expected: "\n>>> [CLI] bundle validate -t dev -o json\n{\n  \"catalogs\": {\n    \"my_catalog\": {\n      \"name\": \"mycatalog\"\n    }\n  },\n  \"feature_specs\": {\n    \"my_feature_spec\": {\n      \"features\": [\n        {\n          \"feature_lookup\": {\n            \"feature_names\": [\n              \"age\"\n            ],\n            \"lookup_keys\": [\n              \"user_id\"\n            ],\n            \"table\": \"mycatalog.myschema.user_features\"\n          }\n        }\n      ],\n      \"name\": \"${resources.catalogs.my_catalog.name}.${resources.schemas.my_schema.name}.myspec\"\n    }\n  },\n  \"schemas\": {\n    \"my_schema\": {\n      \"catalog_name\": \"${resources.catalogs.my_catalog.name}\",\n      \"name\": \"dev_[USERNAME]_myschema\"\n    }\n  }\n}\n"
        	            	actual  : "\n>>> [CLI] bundle validate -t dev -o json\nWarning: unknown field: feature_specs\n  at resources\n  in databricks.yml:14:3\n\n{\n  \"catalogs\": {\n    \"my_catalog\": {\n      \"name\": \"mycatalog\"\n    }\n  },\n  \"schemas\": {\n    \"my_schema\": {\n      \"catalog_name\": \"${resources.catalogs.my_catalog.name}\",\n      \"name\": \"dev_[USERNAME]_myschema\"\n    }\n  }\n}\n"
        	            	
        	            	Diff:
        	            	--- Expected
        	            	+++ Actual
        	            	@@ -2,2 +2,6 @@
        	            	 >>> [CLI] bundle validate -t dev -o json
        	            	+Warning: unknown field: feature_specs
        	            	+  at resources
        	            	+  in databricks.yml:14:3
        	            	+
        	            	 {
        	            	@@ -6,20 +10,2 @@
        	            	       "name": "mycatalog"
        	            	-    }
        	            	-  },
        	            	-  "feature_specs": {
        	            	-    "my_feature_spec": {
        	            	-      "features": [
        	            	-        {
        	            	-          "feature_lookup": {
        	            	-            "feature_names": [
        	            	-              "age"
        	            	-            ],
        	            	-            "lookup_keys": [
        	            	-              "user_id"
        	            	-            ],
        	            	-            "table": "mycatalog.myschema.user_features"
        	            	-          }
        	            	-        }
        	            	-      ],
        	            	-      "name": "${resources.catalogs.my_catalog.name}.${resources.schemas.my_schema.name}.myspec"
        	            	     }
        	Test:       	TestAccept/bundle/resource_deps/implicit_deps_feature_spec/DMS=true
        	Messages:   	bundle/resource_deps/implicit_deps_feature_spec/output.txt vs /tmp/TestAcceptbundleresource_depsimplicit_deps_feature_specDMS=t1461953591/002/output.txt

latest (v1.25.0): same output as main (6bdf874)

TestAccept/bundle/resources/feature_specs/basic/DMS=: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: Diff:
        --- bundle/resources/feature_specs/basic/output.txt
        +++ /tmp/TestAcceptbundleresourcesfeature_specsbasicDMS=612886793/002/output.txt
        @@ -8,174 +8,30 @@
         
         === Initial summary before deploy
         >>> [CLI] bundle summary -o json
        -{
        -  "extra_pip_requirements": {
        -    "requirements": [
        -      "numpy==2.1.0"
        -    ]
        -  },
        -  "features": [
        -    {
        -      "feature_lookup": {
        -        "feature_names": [
        -          "age",
        -          "country"
        -        ],
        -        "lookup_keys": [
        -          "user_id"
        -        ],
        -        "table": "main.fspec_[UNIQUE_NAME].user_features"
        -      }
        -    },
        -    {
        -      "feature_function": {
        -        "input_bindings": [
        -          {
        -            "bound_feature": "age",
        -            "parameter": "x"
        -          }
        -        ],
        -        "output": "score",
        -        "udf": "main.fspec_[UNIQUE_NAME].score"
        -      }
        -    }
        -  ],
        -  "modified_status": "created",
        -  "name": "[SPEC1_ID]"
        -}
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
         
        +null
        +
         >>> [CLI] bundle deploy
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
        +
         Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-basic-[UNIQUE_NAME]/default/files...
        -Created feature_specs.spec1
         Files: 2 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
        +Resources: 0 created, 0 changed, 0 deleted, 0 unchanged
        +Traceback (most recent call last):
        +  File "[TESTROOT]/bin/replace_ids.py", line 68, in <module>
        +    main()
        +    ~~~~^^
        +  File "[TESTROOT]/bin/replace_ids.py", line 63, in main
        +    for name, id in it:
        +                    ^^
        +  File "[TESTROOT]/bin/replace_ids.py", line 18, in iter_ids_terraform
        +    raw = open(filename).read()
        +          ~~~~^^^^^^^^^^
        +FileNotFoundError: [Errno 2] No such file or directory: '.databricks/bundle/default/terraform/terraform.tfstate'
         
        ->>> print_requests.py //feature-engineering
        -{
        -  "method": "POST",
        -  "path": "/api/2.0/feature-engineering/feature-specs",
        -  "body": {
        -    "extra_pip_requirements": {
        -      "requirements": [
        -        "numpy==2.1.0"
        -      ]
        -    },
        -    "features": [
        -      {
        -        "feature_lookup": {
        -          "feature_names": [
        -            "age",
        -            "country"
        -          ],
        -          "lookup_keys": [
        -            "user_id"
        -          ],
        -          "table": "main.fspec_[UNIQUE_NAME].user_features"
        -        }
        -      },
        -      {
        -        "feature_function": {
        -          "input_bindings": [
        -            {
        -              "bound_feature": "age",
        -              "parameter": "x"
        -            }
        -          ],
        -          "output": "score",
        -          "udf": "main.fspec_[UNIQUE_NAME].score"
        -        }
        -      }
        -    ],
        -    "name": "[SPEC1_ID]"
        -  }
        -}
        -
        -=== Summary should show the id and the Catalog Explorer url
        ->>> [CLI] bundle summary -o json
        -{
        -  "id": "[SPEC1_ID]",
        -  "url": "[DATABRICKS_URL]/explore/data/functions/main/fspec_[UNIQUE_NAME]/myspec?w=[WORKSPACE_ID]"
        -}
        -
        -=== A second plan on an unchanged bundle must report no changes
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged
        -
        -=== Change a field (every field is immutable, so this recreates)
        ->>> update_file.py databricks.yml [age, country] [age]
        -
        ->>> [CLI] bundle plan
        -recreate feature_specs.spec1
        -
        -Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged
        -
        ->>> [CLI] bundle deploy --auto-approve
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-basic-[UNIQUE_NAME]/default/files...
        -
        -This action will result in the deletion or recreation of the following feature specs.
        -Grants on them that are not declared in the bundle are lost:
        -  recreate resources.feature_specs.spec1
        -Recreated feature_specs.spec1
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 0 unchanged
        -
        ->>> print_requests.py //feature-engineering
        -{
        -  "method": "DELETE",
        -  "path": "/api/2.0/feature-engineering/feature-specs/[SPEC1_ID]"
        -}
        -{
        -  "method": "POST",
        -  "path": "/api/2.0/feature-engineering/feature-specs",
        -  "body": {
        -    "extra_pip_requirements": {
        -      "requirements": [
        -        "numpy==2.1.0"
        -      ]
        -    },
        -    "features": [
        -      {
        -        "feature_lookup": {
        -          "feature_names": [
        -            "age"
        -          ],
        -          "lookup_keys": [
        -            "user_id"
        -          ],
        -          "table": "main.fspec_[UNIQUE_NAME].user_features"
        -        }
        -      },
        -      {
        -        "feature_function": {
        -          "input_bindings": [
        -            {
        -              "bound_feature": "age",
        -              "parameter": "x"
        -            }
        -          ],
        -          "output": "score",
        -          "udf": "main.fspec_[UNIQUE_NAME].score"
        -        }
        -      }
        -    ],
        -    "name": "[SPEC1_ID]"
        -  }
        -}
        -
        ->>> [CLI] bundle destroy --auto-approve
        -The following resources will be deleted:
        -  delete resources.feature_specs.spec1
        -
        -This action will result in the deletion of the following feature specs.
        -Feature Serving endpoints and training code that use them can no longer load them:
        -  delete resources.feature_specs.spec1
        -
        -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-basic-[UNIQUE_NAME]/default
        -
        -Destroy: 1 deleted
        -
        ->>> print_requests.py //feature-engineering
        -{
        -  "method": "DELETE",
        -  "path": "/api/2.0/feature-engineering/feature-specs/[SPEC1_ID]"
        -}
        +Exit code: 1
... (12 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)

latest (v1.25.0):

acceptance_test.go:1345: Diff:
        --- bundle/resources/feature_specs/basic/output.txt
        +++ /tmp/TestAcceptbundleresourcesfeature_specsbasicDMS=183611421/002/output.txt
        @@ -8,174 +8,30 @@
         
         === Initial summary before deploy
         >>> [CLI] bundle summary -o json
        -{
        -  "extra_pip_requirements": {
        -    "requirements": [
        -      "numpy==2.1.0"
        -    ]
        -  },
        -  "features": [
        -    {
        -      "feature_lookup": {
        -        "feature_names": [
        -          "age",
        -          "country"
        -        ],
        -        "lookup_keys": [
        -          "user_id"
        -        ],
        -        "table": "main.fspec_[UNIQUE_NAME].user_features"
        -      }
        -    },
        -    {
        -      "feature_function": {
        -        "input_bindings": [
        -          {
        -            "bound_feature": "age",
        -            "parameter": "x"
        -          }
        -        ],
        -        "output": "score",
        -        "udf": "main.fspec_[UNIQUE_NAME].score"
        -      }
        -    }
        -  ],
        -  "modified_status": "created",
        -  "name": "[SPEC1_ID]"
        -}
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
         
        +null
        +
         >>> [CLI] bundle deploy
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
        +
         Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-basic-[UNIQUE_NAME]/default/files...
        -Created feature_specs.spec1
         Files: 2 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
        +Resources: 0 created, 0 changed, 0 deleted, 0 unchanged
        +Traceback (most recent call last):
        +  File "[TESTROOT]/bin/replace_ids.py", line 68, in <module>
        +    main()
        +    ~~~~^^
        +  File "[TESTROOT]/bin/replace_ids.py", line 63, in main
        +    for name, id in it:
        +                    ^^
        +  File "[TESTROOT]/bin/replace_ids.py", line 18, in iter_ids_terraform
        +    raw = open(filename).read()
        +          ~~~~^^^^^^^^^^
        +FileNotFoundError: [Errno 2] No such file or directory: '.databricks/bundle/default/terraform/terraform.tfstate'
         
        ->>> print_requests.py //feature-engineering
        -{
        -  "method": "POST",
        -  "path": "/api/2.0/feature-engineering/feature-specs",
        -  "body": {
        -    "extra_pip_requirements": {
        -      "requirements": [
        -        "numpy==2.1.0"
        -      ]
        -    },
        -    "features": [
        -      {
        -        "feature_lookup": {
        -          "feature_names": [
        -            "age",
        -            "country"
        -          ],
        -          "lookup_keys": [
        -            "user_id"
        -          ],
        -          "table": "main.fspec_[UNIQUE_NAME].user_features"
        -        }
        -      },
        -      {
        -        "feature_function": {
        -          "input_bindings": [
        -            {
        -              "bound_feature": "age",
        -              "parameter": "x"
        -            }
        -          ],
        -          "output": "score",
        -          "udf": "main.fspec_[UNIQUE_NAME].score"
        -        }
        -      }
        -    ],
        -    "name": "[SPEC1_ID]"
        -  }
        -}
        -
        -=== Summary should show the id and the Catalog Explorer url
        ->>> [CLI] bundle summary -o json
        -{
        -  "id": "[SPEC1_ID]",
        -  "url": "[DATABRICKS_URL]/explore/data/functions/main/fspec_[UNIQUE_NAME]/myspec?w=[WORKSPACE_ID]"
        -}
        -
        -=== A second plan on an unchanged bundle must report no changes
        ->>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged
        -
        -=== Change a field (every field is immutable, so this recreates)
        ->>> update_file.py databricks.yml [age, country] [age]
        -
        ->>> [CLI] bundle plan
        -recreate feature_specs.spec1
        -
        -Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged
        -
        ->>> [CLI] bundle deploy --auto-approve
        -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-basic-[UNIQUE_NAME]/default/files...
        -
        -This action will result in the deletion or recreation of the following feature specs.
        -Grants on them that are not declared in the bundle are lost:
        -  recreate resources.feature_specs.spec1
        -Recreated feature_specs.spec1
        -Files: 1 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 1 deleted, 0 unchanged
        -
        ->>> print_requests.py //feature-engineering
        -{
        -  "method": "DELETE",
        -  "path": "/api/2.0/feature-engineering/feature-specs/[SPEC1_ID]"
        -}
        -{
        -  "method": "POST",
        -  "path": "/api/2.0/feature-engineering/feature-specs",
        -  "body": {
        -    "extra_pip_requirements": {
        -      "requirements": [
        -        "numpy==2.1.0"
        -      ]
        -    },
        -    "features": [
        -      {
        -        "feature_lookup": {
        -          "feature_names": [
        -            "age"
        -          ],
        -          "lookup_keys": [
        -            "user_id"
        -          ],
        -          "table": "main.fspec_[UNIQUE_NAME].user_features"
        -        }
        -      },
        -      {
        -        "feature_function": {
        -          "input_bindings": [
        -            {
        -              "bound_feature": "age",
        -              "parameter": "x"
        -            }
        -          ],
        -          "output": "score",
        -          "udf": "main.fspec_[UNIQUE_NAME].score"
        -        }
        -      }
        -    ],
        -    "name": "[SPEC1_ID]"
        -  }
        -}
        -
        ->>> [CLI] bundle destroy --auto-approve
        -The following resources will be deleted:
        -  delete resources.feature_specs.spec1
        -
        -This action will result in the deletion of the following feature specs.
        -Feature Serving endpoints and training code that use them can no longer load them:
        -  delete resources.feature_specs.spec1
        -
        -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-basic-[UNIQUE_NAME]/default
        -
        -Destroy: 1 deleted
        -
        ->>> print_requests.py //feature-engineering
        -{
        -  "method": "DELETE",
        -  "path": "/api/2.0/feature-engineering/feature-specs/[SPEC1_ID]"
        -}
        +Exit code: 1
... (12 more lines truncated, raise DATABRICKS_CLI_TEST_MAX_LOG to see more)
TestAccept/bundle/resources/feature_specs/order/DMS=: branch ✅ | main (6bdf874) ❌ | latest (v1.25.0) ❌

main (6bdf874):

acceptance_test.go:1345: Diff:
        --- bundle/resources/feature_specs/order/output.txt
        +++ /tmp/TestAcceptbundleresourcesfeature_specsorderDMS=4014323127/002/output.txt
        @@ -7,29 +7,35 @@
         >>> create_feature_spec_fixtures.py main.fspec_[UNIQUE_NAME]
         
         >>> [CLI] bundle deploy
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
        +
         Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-order-[UNIQUE_NAME]/default/files...
        -Created feature_specs.spec1
         Files: 4 uploaded, 0 deleted
        -Resources: 1 created, 0 changed, 0 deleted, 0 unchanged
        +Resources: 0 created, 0 changed, 0 deleted, 0 unchanged
         
         === Reorder the lists whose order means nothing: no changes
         >>> [CLI] bundle plan
        -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
         
        +Plan: 0 to add, 0 to change, 0 to delete, 0 unchanged
        +
         === Also reorder the lookup keys, which must follow the table's primary key: recreate
         >>> [CLI] bundle plan
        -recreate feature_specs.spec1
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
         
        -Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged
        +Plan: 0 to add, 0 to change, 0 to delete, 0 unchanged
         
         >>> [CLI] bundle destroy --auto-approve
        -The following resources will be deleted:
        -  delete resources.feature_specs.spec1
        +Warning: unknown field: feature_specs
        +  at resources
        +  in databricks.yml:5:3
         
        -This action will result in the deletion of the following feature specs.
        -Feature Serving endpoints and training code that use them can no longer load them:
        -  delete resources.feature_specs.spec1
        -
         All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-feature-specs-order-[UNIQUE_NAME]/default
         
        -Destroy: 1 deleted
        +Destroy: 0 deleted
        
    acceptance_test.go:1123: 
        LOG.cleanup: Warning: unknown field: feature_specs
        LOG.cleanup:   at resources
        LOG.cleanup:   in databricks.yml:5:3
        LOG.cleanup: 
        LOG.cleanup: No active deployment found to destroy!
        LOG.cleanup: Warning: unknown field: feature_specs
        LOG.cleanup:   at resources
        LOG.cleanup:   in databricks.yml:5:3

latest (v1.25.0): same output as main (6bdf874)

4 more failure details omitted, see the full report in the run.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Integration test report

Commit: f34d1a8

Run: 43039071828

Env ✅​pass 🙈​skip Time
✅​ aws linux-2core-8gb 298 18 11:26
✅​ aws-windows-latest-4core-16gb 300 16 16:42
✅​ azure linux-2core-8gb 297 18 13:01
✅​ azure-windows-latest-4core-16gb 299 16 9:57
✅​ gcp linux-2core-8gb 298 18 12:02
✅​ gcp-windows-latest-4core-16gb 300 16 9:10
Top 24 slowest tests (at least 2 minutes):
duration env testname
4:24 azure-windows-latest-4core-16gb TestAccept/bundle/invariant/delete_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN=1
4:18 aws-windows-latest-4core-16gb TestFilerReadWrite/dbfs
3:59 aws linux-2core-8gb TestAccept
3:54 azure linux-2core-8gb TestAccept
3:54 gcp linux-2core-8gb TestAccept
3:35 azure-windows-latest-4core-16gb TestAccept
3:30 aws-windows-latest-4core-16gb TestAccept
3:27 gcp-windows-latest-4core-16gb TestAccept
3:08 aws-windows-latest-4core-16gb TestFsCpDirToDirWithOverwriteFlag/dbfs_to_dbfs
3:00 aws-windows-latest-4core-16gb TestFsCpFileToDirFileNotOverwritten/dbfs_to_dbfs
2:50 aws-windows-latest-4core-16gb TestAccept/bundle/invariant/delete_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN=1
2:45 aws-windows-latest-4core-16gb TestFsCpDir/dbfs_to_local
2:34 gcp linux-2core-8gb TestFilerReadDir/dbfs
2:21 gcp-windows-latest-4core-16gb TestFsCpDir/dbfs_to_dbfs
2:16 azure linux-2core-8gb TestAccept/bundle/invariant/delete_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN=1
2:14 gcp-windows-latest-4core-16gb TestFsCpDirToDirFileNotOverwritten/dbfs_to_dbfs
2:13 azure linux-2core-8gb TestFilerReadWrite/dbfs
2:12 gcp-windows-latest-4core-16gb TestFsCpDir/uc-volumes_to_dbfs
2:08 azure-windows-latest-4core-16gb TestFilerRecursiveDelete/dbfs
2:06 azure linux-2core-8gb TestAccept/bundle/invariant/delete_idempotent/DMS=/INPUT_CONFIG=feature_spec.yml.tmpl/READPLAN=
2:05 azure-windows-latest-4core-16gb TestAccept/bundle/resources/feature_specs/uppercase-name/DMS=
2:01 aws-windows-latest-4core-16gb TestFsCpFileToDirFileNotOverwritten/local_to_dbfs
2:01 aws-windows-latest-4core-16gb TestFsCpDir/local_to_dbfs
2:01 aws-windows-latest-4core-16gb TestFsCpFileToDirFileNotOverwritten/dbfs_to_uc-volumes

Daniel114510 and others added 2 commits November 4, 2026 22:59
Every feature spec case that deploys now sets Cloud = true, and the
invariant matrix no longer skips feature_spec.yml.tmpl on cloud.

Creating a feature spec resolves its lookup tables and UDFs in Unity
Catalog, so each case creates them in a per-run main.fspec_<unique>
schema with create_feature_spec_fixtures.py and drops the schema on
exit. Bundle names carry $UNIQUE_NAME so parallel runs do not share
state. The grants case grants to account users, which exists in every
workspace. The bind case binds a spec created outside the bundle and
plans no change.

The fake's delete now returns {} like the real endpoint.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@Daniel114510 Daniel114510 changed the title Fs2524 feature specs [FS2524] Add Feature Spec as a Bundle Resource Nov 5, 2026
Daniel114510 and others added 5 commits November 5, 2026 17:45
Brings in the Go SDK v0.194.0 bump (#7371), which carries the FeatureSpec
API in .codegen/cli.json. Conflicts were adjacent additions next to the
postgres_data_apis resource (#7160); both sides are kept.

Co-authored-by: Isaac <no-reply@databricks.com>
Regenerated from the FeatureSpec API in .codegen/cli.json (Go SDK
v0.194.0): the bundle JSON schema, required-field validation, the direct
engine API types, the refschema field list and the PyDABs models.
configs/feature_specs.generated.yml regenerates unchanged.

Fills the three schema placeholders the resource adds, following the
features and registered_models entries. The grants example uses EXECUTE,
since a feature spec is a UC function.

Adds the python/feature_specs-support golden.

Co-authored-by: Isaac <no-reply@databricks.com>
main now builds with Go 1.27, whose modernize embedlit check flags the
embedded type name in a struct literal.

Co-authored-by: Isaac <no-reply@databricks.com>
&>> needs bash 4; the macOS runners use bash 3.2, which rejects the
scripts before they run.

Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>
@Daniel114510
Daniel114510 marked this pull request as ready for review November 5, 2026 19:35
@Daniel114510
Daniel114510 requested review from a team as code owners November 5, 2026 19:35
UC lowercases a feature spec's name, and create and get now return it that
way. Listing name in provided_id_fields skips that read-back difference, as
it does for a schema's name, so a mixed-case name no longer recreates the
feature spec on every plan. The fake lowercases the name like the real
endpoint.

Co-authored-by: Isaac <no-reply@databricks.com>
@janniklasrose janniklasrose self-assigned this Nov 9, 2026
Co-authored-by: Isaac <no-reply@databricks.com>
Daniel114510 and others added 2 commits November 11, 2026 01:29
Co-authored-by: Isaac <no-reply@databricks.com>
Co-authored-by: Isaac <no-reply@databricks.com>

@janniklasrose janniklasrose left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nits, none blocking:

  • grants/feature_specs/script:15 and :23 use jq 'del(.features)'; nostamp is the helper that strips the plan header, and the registered_models grants test uses it.
  • messages.go:66 and :122 say "feature specs" while the sibling messages say "Feature Views".
  • python/feature_specs-support/test.toml:1 sets Cloud = true and databricks.yml keeps a grants block; acceptance/bundle/python/README.md asks for Cloud = false and no cloud-only blocks.
  • feature_spec.go:90 says what slices.Clone does to an empty list but not that the function returns a sorted copy.

(Edited: dropped the asks about the cloud run and about restating decisions and API requests in the description. Cloud = true tests run in the PR's Integration Tests check, and the code carries the rest.)

Comment thread bundle/phases/messages.go Resolved
Comment thread bundle/direct/dresources/all_test.go Resolved
Comment thread libs/testserver/feature_spec.go Resolved
janniklasrose added a commit that referenced this pull request Nov 11, 2026
…ource skills

The skills asked for things a hands-off author cannot usefully supply and a reviewer should not demand: a cloud run link and environment in the description, a restatement of API asks and bundle-wide decisions there, and a shared helper whenever a block repeated a sibling's. Running review-new-resource on #7364 produced exactly those comments.

- Cloud = true tests run in the PR's Integration Tests check; that check is the evidence. Neither skill asks for a run link, an environment, or a statement about the run.
- Decisions and API requests live in the code: the comment on the rule or mask, the test list entry, the exclusion line. The description is read for intent only and its completeness is not reviewed.
- Repetition that matches a sibling is the expected result of copying the exemplar. The review skill gets a P10 pass that compares shape with the sibling and asks for an existing helper by name, never for a new one.
- The resource-level launch stage comes only from launchStageOverrides in bundle/internal/annotation/preview.go; cli.json carries stages on fields only. The readiness check now describes that as a CLI decision instead of an upstream ask.

Co-authored-by: Isaac <no-reply@databricks.com>

@andrewnester andrewnester left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nits:

  • bundle/direct/dresources/feature_spec.go:23: "what gets sent and stored in the backend" — the sorted copy is what is sent to the API and persisted in the bundle state; the backend stores whatever it wants. "sent to the API and persisted in state" would be more precise.

@janniklasrose janniklasrose left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving. The design matches precedent on every point: no DoUpdate for an API without one, KeyedSlices keyed on real fields, no plan overrides, approvals on both deploy and destroy, adapter and fake well under the size of the last resources, and the Integration Tests check is green.

One thing worth doing before merge: the Azure/GCP Repls entry on resources/feature_specs/test.toml, so basic matches there the way bind now does. Everything else in my earlier comments is follow-up material.

Resolve the conflict in bundle/schema/jsonschema.json by regenerating it, and regenerate the feature spec PyDABs models under the merged codegen.

Co-authored-by: Isaac <no-reply@databricks.com>
@Daniel114510
Daniel114510 added this pull request to the merge queue Nov 11, 2026
@andrewnester
andrewnester removed this pull request from the merge queue due to a manual request Nov 11, 2026
@andrewnester
andrewnester added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit 7e0f9b4 Nov 11, 2026
31 checks passed
@andrewnester
andrewnester deleted the fs2524-feature-specs branch November 11, 2026 15:16
deco-sdk-tagging Bot added a commit that referenced this pull request Nov 11, 2026
## Release v1.26.0

### AI Runtime

 * `air run` reuses its existing workspace connection for config validation instead of opening a second one, which saves a TCP and TLS handshake per submission. ([#7210](#7210))
 * Speed up `databricks air run` for git-pinned `code_source` snapshots by compressing the archive in parallel, which substantially shortens packaging on large repositories. ([#7402](#7402))
 * Point the `air run -h config` agent-skill hint at the stable `databricks-ai-runtime` skill. ([#7454](#7454))

### Bundles

 * Add `DATABRICKS_BUNDLE_INIT_WORKSPACE_HOST_PROFILE`, which names a profile whose host the `workspace_host` template helper returns instead of the selected profile's host. The profile must have the selected profile's workspace ID. ([#7410](#7410))
 * Fixed an explicit `false` on optional boolean fields (`variables.*.sensitive`, `artifacts.*.dynamic_version`, `bundle.deployment.lock.force`, `workspace.azure_use_msi`, `replace_existing` on postgres endpoints, databases and roles, and the `experimental.*` flags) being dropped, which hid it from `bundle validate -o json` and kept a target from overriding `true` with it. ([#7412](#7412))
 * Stop sending `compute_size` in the app update mask when the bundle does not declare it, so a deploy no longer resets a manually resized app to the default size. ([#7461](#7461))
 * `lifecycle.depends_on` can now target the grants or permissions of a resource, as `schemas.foo.grants`. ([#7435](#7435))
 * Add support for the `feature_specs` resource type, including UC grants on a feature spec. ([#7364](#7364))

### Dependency Updates

 * Update the Go SDK to [v0.197.0](https://github.com/databricks/databricks-sdk-go/releases/tag/v0.197.0). ([#7457](#7457))
 * Bump Go toolchain to 1.27.4. ([#7458](#7458))
@Daniel114510

Copy link
Copy Markdown
Member Author

@janniklasrose @andrewnester Thank you very much for approving and providing the thorough feedbacks!

I opened a followup PR that addresses the proposed feedbacks and changes.

idelafuente-oc pushed a commit to idelafuente-oc/cli that referenced this pull request Nov 14, 2026
## Changes

Addresses the review feedback on databricks#7364 that was still open at merge.
Each item links the comments it answers.
- **Approval prompts**
([Jan](databricks#7364 (comment)),
[Jan's
review](databricks#7364 (review))):
the deploy prompt also names what a deleted FeatureSpec breaks, and both
prompts say "FeatureSpecs", like the Feature View prompts.
- **SQL stubs**
([Jan](databricks#7364 (comment)),
[Andrew](databricks#7364 (comment))):
the test server accepts `POST /api/2.0/sql/statements/` with the
trailing slash the scripts send, and answers `CREATE TABLE`, `CREATE
FUNCTION` and `INSERT INTO` with an empty `SUCCEEDED` result, as a
fallback that a test's own matcher overrides. The `[[Server]]` stubs are
gone from the feature spec, grants, bind, invariant and
`features/lifecycle` tests.
- **Shared fixture helpers**
([Andrew](databricks#7364 (comment))):
the feature spec schema setup and cleanup move to
`acceptance/bundle/script.prepare`, which the feature spec, grants, bind
and invariant tests use.
- **Shared statement runner**
([Andrew](databricks#7364 (comment))):
`create_table.py` and `create_feature_spec_fixtures.py` share
`run_statements` in `acceptance/bin/util.py`, so `create_table.py` now
also waits for each statement and fails if one does not succeed.
- **`resolveThreePartName`**
([Andrew](databricks#7364 (comment))):
the vector search index, feature and feature spec blocks of
`capture_uc_dependencies` share one helper.
- **`?w=` in summary URLs**
([Jan](databricks#7364 (comment)),
[Andrew](databricks#7364 (comment))):
`resources/feature_specs/test.toml` strips the suffix, as `bind` does.
- **Generated commands**
([Jan](databricks#7364 (comment))):
the bind and remote-delete scripts use `feature-engineering
create-feature-spec` and `delete-feature-spec` instead of `databricks
api`.
- **`nostamp`**
([Andrew](databricks#7364 (comment)),
[Jan's
review](databricks#7364 (review))):
the grants test pipes plan JSON through `nostamp`.
- **`TestAll` fixture**
([Jan](databricks#7364 (comment))):
sets every feature spec field.
- **Fake validation**
([Jan](databricks#7364 (comment))):
the test server's feature spec create no longer checks for a three-part
name.
- **PyDABs test** ([Jan's
review](databricks#7364 (review))):
`python/feature_specs-support` runs locally only and has no `grants`,
per `acceptance/bundle/python/README.md`.

## Why

Review feedback on databricks#7364 that was left for after the merge.

## Tests

- Acceptance tests:
- `bundle/resources/feature_specs`,
`bundle/resources/grants/feature_specs`,
`bundle/deployment/bind/feature_spec`: re-recorded for the prompts,
commands and URL
- `bundle/invariant`, `bundle/resources/features/lifecycle`: pass
without the SQL stub
  - `bundle/python/feature_specs-support`: local only, without grants
- Unit tests:
  - `libs/testserver/testsql`: a fallback yields to a test's matcher
- `libs/testserver`: default handlers accept fixture statements on both
paths
- `bundle/direct/dresources`: `TestAll/feature_specs` with every field
set

---------

Co-authored-by: Isaac <no-reply@databricks.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DABs DABs related issues PyDABs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants