ingest/dbt: ephemeral models are excluded from cross-project URN collision detection
Summary
Ephemeral dbt models are excluded from the cross-project identity-collision detection added in #19473, and they can silently overwrite one another.
Mechanism
_check_duplicate_models in metadata-ingestion/src/datahub/ingestion/source/dbt/dbt_common.py filters candidates on node.exists_in_target_platform, which is defined as:
not (self.is_ephemeral_model() or self.node_type == "test")so ephemeral models never reach the check. They do, however, still emit a dbt-platform dataset URN built from get_db_fqn() -> database.schema.name, which carries no project identity. Two dbt projects with a same-named ephemeral model targeting the same schema therefore resolve to one URN and overwrite each other's aspects.
The unique_id collision check does not catch this either: unique_id embeds the package name, so two projects produce distinct ids.
Two related effects follow from the same root cause, since get_fake_ephemeral_table_name() is likewise database.schema.__datahub__dbt__ephemeral__<name> with no project component:
- downstream lineage references resolve to the shared name, and
- schema inference keys ephemeral nodes on it, so column lineage can mix across projects.
Why it was not fixed in #19473
A fix was implemented and then reverted there, deliberately. Including ephemeral models in the check as-is can newly hard-fail a single-project run: dbt skips ephemeral models in its own relation-uniqueness check (they are not relational), so dbt permits an ephemeral model to share a relation name with a materialized one. An installed package's ephemeral model colliding with a local model in the same target schema is a realistic shape. Because a reported collision also suppresses stale-entity soft-deletion for the whole run, that is a disproportionate outcome for a pre-existing single-project condition, and #19473's collision handling is scoped to cross-project fan-out.
Suggested direction
Detect the collision only when the contenders come from different projects, exempting the same-project ephemeral/materialized pairing. Note that DBTNode.manifest_path is None on the dbt Cloud path, which correctly reads as a single project there.
An alternative worth weighing is including the project or package in the ephemeral fake-table name, which would remove the collision at the source rather than reporting it — at the cost of changing existing ephemeral URNs.
Whichever is chosen, the rule established in #19473 applies: an exemption may decide whether a URN is contested, never which nodes escape the consequences once it is.
Context
Found by Cursor Bugbot on #19473. Pre-existing behaviour — not a regression introduced by that PR.
Source: datahub-project/datahub