Blog
Cross-Platform Lineage for Snowflake and dbt: Closing the Transformation Gap

Snowflake and dbt Lineage: Closing the Transformation Gap

November 13, 2026
Subscribe to our Newsletter
Get the latest from our team delivered to your inbox
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Ready to get started?
Try It Free
Snowflake and dbt Lineage cover: bold white headline on a green gradient background

Most lineage tools treat Snowflake and dbt as two systems to document separately instead of one continuous transformation path. A catalog can show that a table in Snowflake feeds a dashboard. It usually cannot show which dbt model built that table, which upstream sources and macros fed the model, or which application code wrote the raw rows dbt started from. That gap matters most the moment something breaks: a renamed column in a source table, a changed join inside a dbt model, or a quietly failing test. Engineering teams end up tracing the path manually, model by model, run by run, while the incident is still open and stakeholders are asking why the numbers changed.

Foundational closes that gap with source code analysis across the full transformation path: the application and pipeline code that writes to Snowflake, the dbt project that transforms it, and every downstream model and exposure that depends on the result. The output is deterministic lineage that spans both platforms as one connected graph, not an inference drawn from query logs. This post covers what table-level metadata misses in a Snowflake and dbt stack, why dbt's own lineage graph stops at the project boundary, and what complete coverage looks like once it is in place.

What Table-Level Metadata Misses in a Snowflake and dbt Stack

Most catalog and lineage tools build their Snowflake-to-dbt view from two sources: Snowflake's query history and dbt's own manifest file. Both are useful, and both stop well short of what an engineering team actually needs during an incident.

What table-level metadata showsWhat it misses
Which dbt model reads from and writes to which Snowflake tableWhat the model's SQL and Jinja macros actually computed to produce each column
That a downstream table changed after a given runWhich upstream application, service, or established system wrote the source rows dbt started from
A column-level map within the warehouseAnything outside Snowflake and dbt: ingestion jobs, reverse ETL, or an application reading the mart directly

None of this is a flaw in dbt or Snowflake. It is a limit of metadata harvested after the fact rather than derived from the code that produced it. A column-level map built from query logs can miss logic that lives inside a conditional macro, a seed file, or a job that runs outside the window the log happened to capture.

Why dbt's Own Lineage Graph Stops at the Project Boundary

dbt's generated DAG is accurate for everything inside the dbt project: model-to-model dependencies, the order models run in, and which downstream models a given change touches. That accuracy has a boundary. The DAG has no visibility into what happens before a raw source lands in Snowflake, and none into what happens after a mart leaves dbt's control through a BI tool, a reverse ETL sync, or an application reading Snowflake directly.

That boundary turns a small upstream change into a long diagnosis. When a source table's schema changes, dbt's lineage correctly shows which models break. It does not show which application, ORM layer, or established system produced that schema change in the first place, so the team still has to go hunting through source code by hand to find root cause. Cross-platform, deterministic lineage, full lineage across every system touching the data, with complete metadata rather than a per-tool snapshot, closes that gap by tracing the same path dbt's DAG stops at, straight back to the code that wrote the original rows.

Deterministic Lineage: The Definition That Matters Here

Deterministic lineage is lineage derived directly from parsing the actual code that moves and transforms data, rather than inferred from query logs or sampled at runtime. Because it comes from the code itself, deterministic lineage reflects every transformation exactly as written, including conditional logic inside a dbt macro or a Python loader, instead of a probabilistic approximation of what most likely happened. For a Snowflake and dbt stack, that means the lineage graph includes the SQL and Jinja inside every dbt model and macro, plus the application and pipeline code that loads Snowflake in the first place, as a single parsed source of truth.

What Good Looks Like

Foundational analyzes the dbt project's SQL and Jinja alongside the application code, ORM layers, and pipeline jobs that write to Snowflake, and produces one deterministic lineage graph across both layers. That is the broader purview at work: any code that touches data, not only the pipeline or the dbt project sitting in front of it. A renamed column in a source application traces forward through every dbt model it touches and out to every downstream exposure, in one graph, instead of requiring a separate lookup in the warehouse and a separate one in the codebase.

This also matters beyond incident response. As dbt models and Snowflake marts increasingly feed AI agents and analytics copilots directly, a lineage graph that stops at the dbt project boundary is also an incomplete context source for those agents. A graph that spans the full transformation path gives agents the same deterministic, code derived provenance a human engineer would use to trust a number, which is the foundation proactive, AI-ready governance depends on.

SuperPlay's BI team saw this directly once cross-repo dependency visibility replaced manual tracing: an 80 percent reduction in PR cycle time and twice as many released PRs, according to Omer Biber, Head of BI. That is the practical effect of replacing a two-system lookup with one graph.

Teams running a similar stack on other platforms see the same pattern; Foundational's approach to closing the application-layer gap in Oracle environments follows the same principle of tracing lineage back to the code, not just the warehouse. For the engineering-workflow side of this, see how teams govern data at the code level instead of relying on post-deployment metadata.

Frequently Asked Questions

Does dbt's own documentation already give us full lineage?

dbt's generated DAG is accurate for everything that happens inside the dbt project: model dependencies, run order, and which models a change touches. It has no visibility into the application and pipeline code that writes to Snowflake before dbt runs, or into what consumes a mart after dbt's job is done. Full cross-platform lineage picks up exactly where that boundary stops.

How is this different from lineage based on Snowflake's query history?

Query-history-based lineage infers relationships from SQL that actually executed, which can miss logic inside views, macros, or jobs that fall outside the log window it happened to capture. Foundational parses the dbt project and the surrounding application code directly, so the resulting lineage is deterministic rather than an inference drawn from a sample of past activity.

Does this replace our dbt tests or CI checks?

No. dbt tests and CI checks validate data at specific points in the pipeline. Deterministic lineage is the map that tells you what a failing test or a broken check actually affects upstream and downstream, across both the dbt project and the code around it, so a failure is faster to diagnose rather than just faster to detect.

What does this look like for a team that also writes directly to Snowflake outside dbt?

Foundational traces any code that touches the warehouse, not only the dbt project. A service that writes to Snowflake directly, an ETL job, or a reverse ETL sync all show up in the same graph as the dbt models they interact with, so the lineage reflects the full stack rather than only the part dbt manages.

Do we need to change how our dbt project is structured to get this?

No. Foundational parses the dbt project and connected code as they already exist. There is no requirement to restructure models, rename macros, or adopt a new convention before lineage coverage applies.

Next Step

If a schema change upstream of Snowflake has ever taken your team longer to trace than to fix, that is the gap cross-platform, deterministic lineage is built to close. Foundational is a data and AI governance platform built on source code analysis, so the graph covers the application code that loads Snowflake and the dbt project that transforms it as one connected map. Request a demo to see Foundational trace a real Snowflake and dbt stack end to end, from application code to the models and exposures it feeds.

code snippet <goes here>
<style>.horizontal-trigger {height: calc(100% - 100vh);}</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.8.0/ScrollTrigger.min.js"></script>
<script>
// © Code by T.RICKS, https://www.timothyricks.com/
// Copyright 2021, T.RICKS, All rights reserved.
// You have the license to use this code in your projects but not to redistribute it to others
gsap.registerPlugin(ScrollTrigger);
let horizontalItem = $(".horizontal-item");
let horizontalSection = $(".horizontal-section");
let moveDistance;
function calculateScroll() {
 // Desktop
 let itemsInView = 3;
 let scrollSpeed = 1.2;  if (window.matchMedia("(max-width: 479px)").matches) {
   // Mobile Portrait
   itemsInView = 1;
   scrollSpeed = 1.2;
 } else if (window.matchMedia("(max-width: 767px)").matches) {
   // Mobile Landscape
   itemsInView = 1;
   scrollSpeed = 1.2;
 } else if (window.matchMedia("(max-width: 991px)").matches) {
   // Tablet
   itemsInView = 2;
   scrollSpeed = 1.2;
 }
 let moveAmount = horizontalItem.length - itemsInView;
 let minHeight =
   scrollSpeed * horizontalItem.outerWidth() * horizontalItem.length;
 if (moveAmount <= 0) {
   moveAmount = 0;
   minHeight = 0;
   // horizontalSection.css('height', '100vh');
 } else {
   horizontalSection.css("height", "200vh");
 }
 moveDistance = horizontalItem.outerWidth() * moveAmount;
 horizontalSection.css("min-height", minHeight + "px");
}
calculateScroll();
window.onresize = function () {
 calculateScroll();
};let tl = gsap.timeline({
 scrollTrigger: {
   trigger: ".horizontal-trigger",
   // trigger element - viewport
   start: "top top",
   end: "bottom top",
   invalidateOnRefresh: true,
   scrub: 1
 }
});
tl.to(".horizontal-section .list", {
 x: () => -moveDistance,
 duration: 1
});
</script>

See Your Full Snowflake and dbt Lineage

Request a demo to trace a real Snowflake and dbt stack from application code to every downstream model.

See Your Full Snowflake and dbt Lineage

Request a demo to trace a real Snowflake and dbt stack from application code to every downstream model.

See Your Full Snowflake and dbt Lineage

Request a demo to trace a real Snowflake and dbt stack from application code to every downstream model.

Share this post
Subscribe to our Newsletter
Get the latest from our team delivered to your inbox
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Ready to get started?
Try It Free

Governance that starts at the source.