Data & Analytics
A dbt model contract can miss a breaking change inside STRUCT
A nested field type change may pass a model-version breaking-change check. Compare normalized schemas and downstream behavior before release.
October 27, 2026
A model version passes a contract check, then a consumer fails because a nested value changed type. A recent dbt-core issue reproduces this with STRUCT(a INTEGER) becoming STRUCT(a VARCHAR) and a Nullable type change. In the reported version, the breaking-change comparison normalizes away the parameterized detail. This is a specific open issue, not proof that every adapter or contract check behaves the same way.
Identify the boundary of the contract
List the fields whose exact nested structure is part of the consumer promise. Review dbt's model contract configuration and model version properties for the behavior your project relies on. A successful dbt build confirms the version's declared shape under its checks; it may not prove that a new version preserves every nested subtype from the old version.
Add a comparison where consumers can break
Before promotion, capture the compiled or warehouse-resolved schemas of old and new model versions. Compare nested fields recursively, including nullability and precision, and classify any differences with consumer owners. Run a representative query from each critical downstream model or dashboard. This is particularly useful for arrays of structs and platform-specific types whose human-readable names hide parameters.
Release with evidence
If the nested type must change, publish it as an intentional versioned interface and give consumers a migration path. If it should not change, block release until the query and declaration agree. Keep the dbt-core issue pinned in the engineering record and retest the minimal reproduction after a future upgrade; do not remove the extra check merely because a release note sounds related.
Adovo’s view
A contract is valuable only at the depth where a customer or system depends on it. A green build is evidence about the check that ran, not a promise about details the check did not inspect.
