In this article ⏷

Lineage You Can Argue With

Column Lineage That Labels The Evidence Behind Every Line

October 19, 2026

An auditor points at a line on your lineage diagram and asks a fair question: how do you know? The line says CustomerID in staging came from CustomerID in the source. Did someone map it on purpose? Did a tool see two columns with the same name and connect them? Did it read an expression and pick the name out? If the diagram can't answer, you're defending a picture. An audit trail has to show its reasons.

‍

BimlFlex Model Lineage answers it on every hop. It builds column-level lineage from your metadata and the published build snapshot, and it attaches the evidence to each line: an authored mapping, a key composition, an expression reference, a name match, or an object-level flow. Where it finds no evidence, it draws nothing. That last rule matters more than it sounds.

‍

We've written before about data lineage best practices in general terms: why lineage should come out of metadata instead of a spreadsheet someone maintains by hand. This post is narrower. It covers design-time, column-level lineage: what the model says will flow where, before anything runs. It isn't about run history or package order in the BimlCatalog. And it's mostly about one question, which is how much you should trust a particular line.

‍

A Line Is a Claim

‍

Every edge on a lineage graph claims that data in one column depends on data in another. Plenty of tools draw these claims. Fewer tell you where a given claim came from, and that gap stays hidden until the day something breaks.

‍

Say a regulatory report shows the wrong customer segment. You trace Segment upstream and find a clean path back to a source column. Is that path real? If a person authored the mapping, the path is as good as your metadata. If the tool inferred it because two columns shared a name, it might be right, and it might be a coincidence that survived three refactors. Those two cases need different conversations, and a diagram that draws them the same way can't tell you which one you're in.

‍

So Model Lineage treats the kind of evidence as part of the edge itself. Two lines between the same pair of columns, one from a mapping and one from a name match, are two separate edges.

‍

Five Kinds of Evidence

‍

Open Model Lineage (the Lineage tab in Diagrams, next to Schema and Data Flow, or search "Model Lineage" in the command palette) and switch to the Text view. Pick an object or one of its columns, choose Upstream or Downstream, and every hop in the trace carries a chip naming the evidence behind it.

‍

mapping: A column mapping someone authored: Your column mapping metadata: this source column targets that column: As much as your metadata. Someone wrote it on purpose.

‍

key: The column is a key built from other columns: A FlexToBk(...) or FlexToHash(...) expression on the column; each argument becomes an input: High. The arguments are the key's real inputs.

‍

expression: The column's expression mentions another column: The column's SQL Source Expression or Dataflow Expression: Medium. It's read from the text, not executed.

‍

name match: Same column name on both sides of an object flow: The generated table's source object has a column with the same name, compared case-insensitively: Low to medium. It's an inference.

‍

flow: One object feeds another: The build snapshot records which object a generated table came from, or an authored mapping connects the two objects: Object-level only. It says nothing about columns.

‍

A few details decide how these behave in practice.

‍

Key columns get their own evidence and nothing else. If a column's expression is FlexToBk(...) or FlexToHash(...), the trace draws one key hop per argument and skips the name-match and expression checks for that column. Each argument resolves to a column on the same object first, then to the object it was staged from. The record source token @@rs is dropped, since it isn't a column. So the inputs you see for a key are the columns that actually go into it.

‍

Expression hops are lexical. BimlFlex looks for known column names as whole words in the expression, case-insensitively, after stripping quoted string literals and @@ tokens. It doesn't parse or run the SQL. If you've written custom column-level transformations, their references show up as expression hops, and a column name that only appears inside a SQL comment would still count. That's the reason the chip says expression and not mapping. An expression cut short in the snapshot is kept for display but never parsed.

‍

Name matches are scoped. A name match is only tried across an object flow the build itself recorded, such as a source object and the staging table generated from it. The trace never compares every column in the model against every other one looking for a matching name. Loads between layers, say staging into a hub or a satellite, only get column lineage from authored mappings, keys and expressions.

‍

No Evidence, No Line

‍

Here's the rule that makes the rest trustworthy. When a column has no mapping, no key expression, no expression reference and no same-named column across its object flow, Model Lineage draws no inbound edge. The upstream trace for that column is just the column.

‍

That's deliberate, and it shows up in two places.

‍

Renamed columns come first. If staging calls it CustomerNumber and the source calls it CustNo, and no mapping or expression connects them, the trace won't guess. It shows CustomerNumber with no known inputs.

‍

System and audit columns come second. A load timestamp or a row hash that the templates add has no source column to point at, so it doesn't get a fake one.

‍

A lineage tool that fills those gaps with plausible lines looks more complete. It's also wrong in the places you're least likely to check. An empty upstream trace is information: this column's origin isn't recorded in metadata. For an audit, that's a finding you can act on. A confident line to the wrong column is a liability you won't notice.

‍

The same honesty applies to objects. Each object in the trace is marked by where it comes from. Objects you authored in metadata are unmarked and link straight to the Object Editor. Tables the build templates create, like staging and persistent staging copies of a source, carry a generated badge ("Generated by build templates, not authored in metadata"). Objects the snapshot references that don't exist in your metadata carry an external badge. You never have to wonder why you can't open a template artifact in the editor.

‍

Reading a Trace

‍

Here's a simplified sketch of the Text view for an upstream trace of a business key column on a staged customer table. The object and column names are illustrative.

‍

Customer_BK 

└── CustomerID Customer (staging) generated key FlexToBk(@@rs, CustomerID) 

└── CustomerID SalesLT.Customer name match 

‍

Read it bottom to top as a chain of claims. The key column is built from CustomerID on the same staged table, and that hop is key evidence taken straight from the key expression. Staging CustomerID came from source CustomerID, and that hop is a name match. The first hop is solid. The second is an inference you'd want to confirm before you hand this to an auditor.

‍

Two more things in the trace view help when it's your turn to answer questions. Select a column and a panel lists every Contributing column upstream, or every Impacted column downstream, grouped by integration stage with counts, so "what does changing this touch" has an actual answer. And the toolbar shows which build snapshot the trace was built from, plus whether that snapshot is stale. If you're saving lineage as evidence, note the snapshot next to it.

‍

The Diagram view draws the same graph for the whole model. It mutes generated and external objects so authored ones stand out, and it marks key and expression edges with an ƒx chip you can hover to see the expression. When two kinds of evidence connect the same pair of columns, the diagram draws one line. The Text view is where each hop's evidence is spelled out, so use it when provenance is the question.

‍

Firming Up the Weak Hops

‍

Tracing a critical column and finding a name match in the chain doesn't mean it's wrong. It means nobody has said it's right. Here's how to fix that.

‍

Start from what matters. Trace your regulated or customer-facing columns upstream to the source, and write down every hop that isn't mapping or key.

‍

For each name match you rely on, confirm it and author the column mapping. The data mapping strategies that work at scale are the ones where the mapping lives in metadata. Once it's there, Refresh the trace. The authored side is read from your current metadata, so a new mapping hop appears without waiting for another build.

‍

For each column with no known inputs that should have one, the gap is in the metadata itself. Usually it's a rename with no mapping behind it, or a derived value whose expression doesn't name its inputs. Add the mapping, or rewrite the expression so it references its source columns directly.

‍

For expression hops on sensitive columns, read the expression. The hop tells you a column name appears in it. It doesn't tell you how the value is used.

‍

Leave template columns alone. A generated audit column with no inputs is correct.

‍

Do this on a schedule, not once. Metadata drifts, columns get renamed, and a hop that was a mapping last quarter can turn into a gap after a source change. At enterprise scale, lineage is only as good as the last time someone checked the weak parts of it.

‍

What It Doesn't Claim

‍

It's worth being precise about the edges of this, since precision is the whole point.

‍

Model Lineage is design-time. It reads your metadata and the build snapshot BimlFlex publishes from a build preview. If no snapshot has been published yet, it says so and links you to Build Preview. It doesn't observe running loads, and it can't see logic that lives outside BimlFlex metadata, like a hand-written view in the target database.

‍

Expression evidence is textual. Name-match evidence is an inference. Both are labeled that way on purpose, so nobody mistakes them for authored mappings.

‍

None of that makes the lineage less useful. It makes it arguable. When someone asks how you know, you can point at the chip, and when the chip says name match, you already know what to go fix.