feat: column-level impact answer in `impact_analysis`, with one column resolver for `dbt_lineage`

RA
2h ago
+2093-70

Issue for this PR

Closes #1709

Type of change

  • Bug fix
  • New feature

What does this PR do?

Asked about a column, impact_analysis says in metadata.column_answer where its value goes (or comes from), hop by hop, and what it couldn't settle; dbt_lineage shares its column resolver; a missing column is an error. Claims and residuals: the review-log comment.

How did you verify your code works?

New tests fail on the previous head; typecheck and Marker Guard pass.

TypeScript job: 5 of 20,967 fail, all timing assertions outside this PR: 4 × redaction performance (fails on main too, #1665) and lintSqlResult's 150 ms bound (215 ms on CI, passes locally 3/3).

Cost side effects

None (local manifest only).

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR
  • I checked for cost side effects (model calls, queries or cloud resources that grow with user data) and described them, or wrote "none"

🤖 Generated with Claude Code

https://claude.ai/code/session_01VEmKQwEiHMwQ7BdwgPo9sb


Summary by cubic

With a column, impact_analysis now returns metadata.column_answer: where the column's value goes downstream (or comes from upstream), hop by hop, and what the engine couldn't settle. column_reach and the text report keep their shape. dbt_lineage and the reach now share one column resolver that works out a parent's undeclared columns from its own SQL.

New Features

  • The answer lists reached nodes, columns, and edges with how each hop changes the value (=, ~, ƒ, Σ, ?), ends, and tiers for what couldn't be settled (also_reads, unresolved with reasons, shape flags), plus cleared models, capped at 2,000 columns; when the cap drops a model's column, it and everything downstream of it count as "may carry", never cleared. Edges are sorted so the answer is identical between runs, and past the structured-result cap column_answer is dropped first so the model-level fields stay.
  • dbt_lineage reads parents through the same resolver, so a select * over an undeclared parent still resolves when its SQL shows the columns; when a *—even inside a function like hash(*)—reads a parent with no known columns, the result is "low" and names it.
  • A column the model's settled columns don't include is now an error in both tools instead of an empty reach; the model-level impact still stands.
  • Exposures report the project's declared count and carry the owner and URL.
  • Parents the engine reads only partially, whose columns it can't list, or that are ephemeral are unresolved, never cleared. Tests that share the engine mock and telemetry state restore them after themselves.

Summary by CodeRabbit

  • New Features
    • Impact analysis can trace a selected column upstream or downstream, with downstream as the default.
    • Results can include column-level lineage, related models, exposure consumers, declared exposure counts, and exposure ownership or URLs.
  • Improvements
    • Lineage can use inferred columns and better identify uncertain tracing, including wildcard-based projections.
    • Column matching is case-insensitive, and missing-column errors can list available columns.
    • Oversized results retain reach and model details while omitting the column answer and explaining the size limit.